备份CatanBuilding瘦身独立工程
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using com.fpnn.proto;
|
||||
|
||||
namespace com.fpnn
|
||||
{
|
||||
public delegate void AnswerDelegate(Answer answer, int errorCode);
|
||||
|
||||
public interface IAnswerCallback
|
||||
{
|
||||
void OnAnswer(Answer answer);
|
||||
void OnException(Answer answer, int errorCode);
|
||||
}
|
||||
|
||||
internal class AnswerDelegateCallback: IAnswerCallback
|
||||
{
|
||||
private AnswerDelegate callback;
|
||||
|
||||
public AnswerDelegateCallback(AnswerDelegate answerDelegate)
|
||||
{
|
||||
callback = answerDelegate;
|
||||
}
|
||||
|
||||
public void OnAnswer(Answer answer)
|
||||
{
|
||||
callback(answer, answer.IsException() ? answer.ErrorCode() : ErrorCode.FPNN_EC_OK);
|
||||
}
|
||||
public void OnException(Answer answer, int errorCode)
|
||||
{
|
||||
callback(answer, errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SyncAnswerCallback: IAnswerCallback
|
||||
{
|
||||
private ManualResetEvent syncEvent;
|
||||
private Answer answer;
|
||||
private UInt32 seqNum;
|
||||
|
||||
public SyncAnswerCallback(Quest quest)
|
||||
{
|
||||
syncEvent = new ManualResetEvent(false);
|
||||
seqNum = quest.SeqNum();
|
||||
}
|
||||
|
||||
public Answer GetAnswer()
|
||||
{
|
||||
syncEvent.WaitOne();
|
||||
syncEvent.Close();
|
||||
return answer;
|
||||
}
|
||||
|
||||
public void OnAnswer(Answer answer)
|
||||
{
|
||||
this.answer = answer;
|
||||
syncEvent.Set();
|
||||
}
|
||||
public void OnException(Answer answer, int errorCode)
|
||||
{
|
||||
if (answer != null)
|
||||
this.answer = answer;
|
||||
else
|
||||
{
|
||||
this.answer = new Answer(seqNum);
|
||||
this.answer.FillErrorCode(errorCode);
|
||||
}
|
||||
|
||||
syncEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e58c58107c454801a373f00fa94e3e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Threading;
|
||||
using com.fpnn.proto;
|
||||
using System;
|
||||
namespace com.fpnn
|
||||
{
|
||||
internal class AdvancedAnswerInfo
|
||||
{
|
||||
public Quest quest;
|
||||
public TCPConnection connection;
|
||||
|
||||
private static ThreadLocal<AdvancedAnswerInfo> instance = new ThreadLocal<AdvancedAnswerInfo>(() => { return new AdvancedAnswerInfo(); });
|
||||
|
||||
public static void Reset(TCPConnection conn, Quest quest)
|
||||
{
|
||||
AdvancedAnswerInfo ins = instance.Value;
|
||||
ins.quest = quest;
|
||||
ins.connection = conn;
|
||||
}
|
||||
|
||||
public static TCPConnection TakeConnection()
|
||||
{
|
||||
AdvancedAnswerInfo ins = instance.Value;
|
||||
TCPConnection conn = ins.connection;
|
||||
ins.connection = null;
|
||||
return conn;
|
||||
}
|
||||
|
||||
public static AdvancedAnswerInfo Get()
|
||||
{
|
||||
return instance.Value;
|
||||
}
|
||||
|
||||
public static bool Answered()
|
||||
{
|
||||
AdvancedAnswerInfo ins = instance.Value;
|
||||
bool answered = ins.connection == null;
|
||||
ins.connection = null;
|
||||
ins.quest = null;
|
||||
return answered;
|
||||
}
|
||||
}
|
||||
|
||||
public class AsyncAnswer
|
||||
{
|
||||
private bool sent;
|
||||
private object interLocker;
|
||||
private Quest quest;
|
||||
private TCPConnection connection;
|
||||
|
||||
private AsyncAnswer()
|
||||
{
|
||||
sent = false;
|
||||
interLocker = new object();
|
||||
}
|
||||
|
||||
~AsyncAnswer()
|
||||
{
|
||||
if (!sent)
|
||||
{
|
||||
Answer answer = new Answer(quest);
|
||||
answer.FillErrorInfo(ErrorCode.FPNN_EC_CORE_UNKNOWN_ERROR, "No answer created by logic.");
|
||||
SendAnswer(answer);
|
||||
}
|
||||
}
|
||||
|
||||
public static AsyncAnswer Create()
|
||||
{
|
||||
AdvancedAnswerInfo info = AdvancedAnswerInfo.Get();
|
||||
if (info.connection == null)
|
||||
return null;
|
||||
|
||||
AsyncAnswer async = new AsyncAnswer();
|
||||
async.connection = info.connection;
|
||||
async.quest = info.quest;
|
||||
|
||||
info.connection = null;
|
||||
return async;
|
||||
}
|
||||
|
||||
public bool SendAnswer(Answer answer)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
if (sent)
|
||||
return false;
|
||||
else
|
||||
sent = true;
|
||||
}
|
||||
|
||||
connection.SendAnswer(answer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdvanceAnswer
|
||||
{
|
||||
public static bool SendAnswer(Answer answer)
|
||||
{
|
||||
TCPConnection conn = AdvancedAnswerInfo.TakeConnection();
|
||||
if (conn != null)
|
||||
{
|
||||
conn.SendAnswer(answer);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20db4fa13ef9a4ac185ee098ce20b9cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,281 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b5292e70c9e04ac980bcf0e98619904
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace com.fpnn
|
||||
{
|
||||
public static partial class ClientEngine
|
||||
{
|
||||
/*
|
||||
static partial void PlatformInit()
|
||||
{
|
||||
}
|
||||
|
||||
static partial void PlatformUninit()
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8415179750e914bc49cf91b97cf509d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
namespace com.fpnn
|
||||
{
|
||||
public class Config
|
||||
{
|
||||
public static readonly string Version = "2.0.9";
|
||||
|
||||
//----------------[ Nested Structure ]-----------------------//
|
||||
public struct TaskThreadPoolConfig
|
||||
{
|
||||
public int initThreadCount;
|
||||
public int perfectThreadCount;
|
||||
public int maxThreadCount;
|
||||
public int maxQueueLengthLimitation;
|
||||
public int tempLatencySeconds;
|
||||
}
|
||||
|
||||
//----------------[ Customized Fields ]-----------------------//
|
||||
|
||||
public TaskThreadPoolConfig taskThreadPoolConfig;
|
||||
public int globalConnectTimeoutSeconds;
|
||||
public int globalQuestTimeoutSeconds;
|
||||
public int maxPayloadSize;
|
||||
public common.ErrorRecorder errorRecorder;
|
||||
public bool dropAllUnexecutedTaskWhenExiting;
|
||||
public bool closeConnectionsWhenBackground;
|
||||
|
||||
public Config()
|
||||
{
|
||||
taskThreadPoolConfig.initThreadCount = 1;
|
||||
taskThreadPoolConfig.perfectThreadCount = 8;
|
||||
taskThreadPoolConfig.maxThreadCount = 16;
|
||||
taskThreadPoolConfig.maxQueueLengthLimitation = 0;
|
||||
taskThreadPoolConfig.tempLatencySeconds = 60;
|
||||
|
||||
globalConnectTimeoutSeconds = 5;
|
||||
globalQuestTimeoutSeconds = 5;
|
||||
maxPayloadSize = 1024 * 1024 * 4; //-- 4MB
|
||||
|
||||
closeConnectionsWhenBackground = false;
|
||||
#if UNITY_EDITOR
|
||||
dropAllUnexecutedTaskWhenExiting = true;
|
||||
#else
|
||||
dropAllUnexecutedTaskWhenExiting = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce8091be660e14f26897422b2c8249ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
namespace com.fpnn
|
||||
{
|
||||
public static class ErrorCode
|
||||
{
|
||||
public const int FPNN_EC_OK = 0;
|
||||
|
||||
//for proto
|
||||
public const int FPNN_EC_PROTO_UNKNOWN_ERROR = 10001;
|
||||
public const int FPNN_EC_PROTO_NOT_SUPPORTED = 10002;
|
||||
public const int FPNN_EC_PROTO_INVALID_PACKAGE = 10003;
|
||||
public const int FPNN_EC_PROTO_JSON_CONVERT = 10004;
|
||||
public const int FPNN_EC_PROTO_STRING_KEY = 10005;
|
||||
public const int FPNN_EC_PROTO_MAP_VALUE = 10006;
|
||||
public const int FPNN_EC_PROTO_METHOD_TYPE = 10007;
|
||||
public const int FPNN_EC_PROTO_PROTO_TYPE = 10008;
|
||||
public const int FPNN_EC_PROTO_KEY_NOT_FOUND = 10009;
|
||||
public const int FPNN_EC_PROTO_TYPE_CONVERT = 10010;
|
||||
public const int FPNN_EC_PROTO_FILE_SIGN = 10011;
|
||||
public const int FPNN_EC_PROTO_FILE_NOT_EXIST = 10012;
|
||||
|
||||
//for core
|
||||
public const int FPNN_EC_CORE_UNKNOWN_ERROR = 20001;
|
||||
public const int FPNN_EC_CORE_CONNECTION_CLOSED = 20002;
|
||||
public const int FPNN_EC_CORE_TIMEOUT = 20003;
|
||||
public const int FPNN_EC_CORE_UNKNOWN_METHOD = 20004;
|
||||
public const int FPNN_EC_CORE_ENCODING = 20005;
|
||||
public const int FPNN_EC_CORE_DECODING = 20006;
|
||||
public const int FPNN_EC_CORE_SEND_ERROR = 20007;
|
||||
public const int FPNN_EC_CORE_RECV_ERROR = 20008;
|
||||
public const int FPNN_EC_CORE_INVALID_PACKAGE = 20009;
|
||||
public const int FPNN_EC_CORE_HTTP_ERROR = 20010;
|
||||
public const int FPNN_EC_CORE_WORK_QUEUE_FULL = 20011;
|
||||
public const int FPNN_EC_CORE_INVALID_CONNECTION = 20012;
|
||||
public const int FPNN_EC_CORE_FORBIDDEN = 20013;
|
||||
public const int FPNN_EC_CORE_SERVER_STOPPING = 20014;
|
||||
|
||||
//for other
|
||||
public const int FPNN_EC_ZIP_COMPRESS = 30001;
|
||||
public const int FPNN_EC_ZIP_DECOMPRESS = 30002;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62c99441214ec484f8546f544497416e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using com.fpnn.proto;
|
||||
namespace com.fpnn
|
||||
{
|
||||
internal class ReceiverErrorMessageException : Exception
|
||||
{
|
||||
public ReceiverErrorMessageException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
public abstract class ReceiverBase
|
||||
{
|
||||
public byte[] buffer;
|
||||
public int offset;
|
||||
public int requireLength;
|
||||
|
||||
public abstract void Done(out Quest quest, out Answer answer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 411914f28889547d2a19e66e5761c079
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using com.fpnn.proto;
|
||||
using com.fpnn.msgpack;
|
||||
namespace com.fpnn
|
||||
{
|
||||
internal class StandardReceiver : ReceiverBase
|
||||
{
|
||||
private const int FPNNHeaderLength = 12;
|
||||
|
||||
private bool receivingHeader;
|
||||
private byte[] headerBuffer;
|
||||
private byte[] bodyBuffer;
|
||||
|
||||
private int payloadLength;
|
||||
private bool isAnswer;
|
||||
private bool isTwowayQuest;
|
||||
|
||||
public StandardReceiver()
|
||||
{
|
||||
headerBuffer = new byte[FPNNHeaderLength];
|
||||
|
||||
ChangeToReceiveHeader();
|
||||
}
|
||||
|
||||
private void ChangeToReceiveHeader()
|
||||
{
|
||||
receivingHeader = true;
|
||||
buffer = headerBuffer;
|
||||
offset = 0;
|
||||
requireLength = FPNNHeaderLength;
|
||||
}
|
||||
|
||||
private void ProcessHeader()
|
||||
{
|
||||
if ((headerBuffer[0] != 0x46)
|
||||
|| (headerBuffer[1] != 0x50)
|
||||
|| (headerBuffer[2] != 0x4e)
|
||||
|| (headerBuffer[3] != 0x4e))
|
||||
{
|
||||
throw new ReceiverErrorMessageException("Package is not FPNN package, magic code mismatched.");
|
||||
}
|
||||
|
||||
if (headerBuffer[5] != 0x80)
|
||||
throw new ReceiverErrorMessageException("Package is not encoded by msgpack.");
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
payloadLength = BitConverter.ToInt32(headerBuffer, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(headerBuffer, 8, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
payloadLength = BitConverter.ToInt32(lengthBuffer, 0);
|
||||
}
|
||||
|
||||
if (payloadLength < 1 || payloadLength > ClientEngine.maxPayloadSize)
|
||||
throw new ReceiverErrorMessageException("Received invalid package, package payload length: " + payloadLength);
|
||||
|
||||
byte mtype = headerBuffer[6];
|
||||
if (mtype == 2)
|
||||
{
|
||||
isAnswer = true;
|
||||
requireLength = payloadLength + 4;
|
||||
}
|
||||
else if (mtype == 1)
|
||||
{
|
||||
isAnswer = false;
|
||||
isTwowayQuest = true;
|
||||
requireLength = payloadLength + 4 + headerBuffer[7];
|
||||
}
|
||||
else if (mtype == 0)
|
||||
{
|
||||
isAnswer = false;
|
||||
isTwowayQuest = false;
|
||||
requireLength = payloadLength + headerBuffer[7];
|
||||
}
|
||||
else
|
||||
throw new ReceiverErrorMessageException("Received invalid package, mtype is " + mtype);
|
||||
|
||||
//-- Change to receive payload.
|
||||
|
||||
if (bodyBuffer == null)
|
||||
bodyBuffer = new byte[requireLength];
|
||||
else if (bodyBuffer.Length < requireLength)
|
||||
bodyBuffer = new byte[requireLength];
|
||||
|
||||
receivingHeader = false;
|
||||
buffer = bodyBuffer;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
private UInt32 FetchSeqNum()
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
return BitConverter.ToUInt32(bodyBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] seqNumBuffer = new byte[4];
|
||||
Array.Copy(bodyBuffer, 0, seqNumBuffer, 0, 4);
|
||||
Array.Reverse(seqNumBuffer);
|
||||
|
||||
return BitConverter.ToUInt32(seqNumBuffer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private Answer BuildAnswer()
|
||||
{
|
||||
Dictionary<Object, Object> payload = MsgUnpacker.Unpack(bodyBuffer, 4, requireLength - 4);
|
||||
bool isErrorAnswer = (headerBuffer[7] != 0);
|
||||
|
||||
return new Answer(FetchSeqNum(), isErrorAnswer, payload);
|
||||
}
|
||||
|
||||
private Quest BuildQuest()
|
||||
{
|
||||
string method;
|
||||
UInt32 seqNum;
|
||||
Dictionary<Object, Object> payload;
|
||||
|
||||
UTF8Encoding utf8Encoding = new UTF8Encoding(false, true); //-- NO BOM.
|
||||
|
||||
if (isTwowayQuest)
|
||||
{
|
||||
seqNum = FetchSeqNum();
|
||||
method = utf8Encoding.GetString(bodyBuffer, 4, headerBuffer[7]);
|
||||
payload = MsgUnpacker.Unpack(bodyBuffer, 4 + headerBuffer[7], requireLength - 4 - headerBuffer[7]);
|
||||
}
|
||||
else
|
||||
{
|
||||
seqNum = 0;
|
||||
method = utf8Encoding.GetString(bodyBuffer, 0, headerBuffer[7]);
|
||||
payload = MsgUnpacker.Unpack(bodyBuffer, headerBuffer[7], requireLength - headerBuffer[7]);
|
||||
}
|
||||
|
||||
return new Quest(method, !isTwowayQuest, seqNum, payload);
|
||||
}
|
||||
|
||||
public override void Done(out Quest quest, out Answer answer)
|
||||
{
|
||||
quest = null;
|
||||
answer = null;
|
||||
|
||||
if (receivingHeader)
|
||||
{
|
||||
if (offset == requireLength)
|
||||
ProcessHeader();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset != requireLength)
|
||||
return;
|
||||
|
||||
if (isAnswer)
|
||||
answer = BuildAnswer();
|
||||
else
|
||||
quest = BuildQuest();
|
||||
|
||||
//-- Change to receive header.
|
||||
|
||||
ChangeToReceiveHeader();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42152b3f9a08f42e4bad736798b3a6a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using com.fpnn.proto;
|
||||
|
||||
/*
|
||||
* TCPConnection 需要在连接事件完成后,抛弃连接事件代理,避免长时间持有client的引用,导致无法释放资源
|
||||
* TCPClient 需要在状态转换后,判断是否放弃当前的TCPConnection对象,避免资源持续相互引用,无法释放
|
||||
*/
|
||||
|
||||
namespace com.fpnn
|
||||
{
|
||||
/*
|
||||
* Connection events.
|
||||
*/
|
||||
public delegate void ConnectionConnectedDelegate(Int64 connectionId, string endpoint, bool connected);
|
||||
public delegate void ConnectionCloseDelegate(Int64 connectionId, string endpoint, bool causedByError);
|
||||
|
||||
/*
|
||||
* Process server pushed quests and return answers if necessary.
|
||||
*/
|
||||
public delegate Answer QuestProcessDelegate(Int64 connectionId, string endpoint, Quest quest);
|
||||
|
||||
public interface IQuestProcessor
|
||||
{
|
||||
QuestProcessDelegate GetQuestProcessDelegate(string method);
|
||||
}
|
||||
|
||||
/*
|
||||
* Recommend that call Close() method when client instance is no longer used.
|
||||
* If this method haven't been called, the client instance will be existed until which connection is broken.
|
||||
*/
|
||||
public class TCPClient
|
||||
{
|
||||
public enum ClientStatus
|
||||
{
|
||||
Closed,
|
||||
Connecting,
|
||||
Connected
|
||||
}
|
||||
|
||||
//----------------[ fields ]-----------------------//
|
||||
|
||||
private object interLocker;
|
||||
private readonly DnsEndPoint dnsEndpoint;
|
||||
public volatile int ConnectTimeout;
|
||||
public volatile int QuestTimeout;
|
||||
public volatile bool AutoConnect;
|
||||
|
||||
private ClientStatus status;
|
||||
private ManualResetEvent syncConnectingEvent;
|
||||
private TCPConnection connection;
|
||||
private ConnectionConnectedDelegate connectConnectedDelegate;
|
||||
private ConnectionCloseDelegate connectionCloseDelegate;
|
||||
private IQuestProcessor questProcessor;
|
||||
|
||||
private common.ErrorRecorder errorRecorder;
|
||||
|
||||
//----------------[ Constructor ]-----------------------//
|
||||
public TCPClient(string host, int port, bool autoConnect = true)
|
||||
{
|
||||
interLocker = new object();
|
||||
dnsEndpoint = new DnsEndPoint(host, port);
|
||||
ConnectTimeout = 0;
|
||||
QuestTimeout = 0;
|
||||
AutoConnect = autoConnect;
|
||||
status = ClientStatus.Closed;
|
||||
syncConnectingEvent = new ManualResetEvent(false);
|
||||
|
||||
errorRecorder = ClientEngine.errorRecorder;
|
||||
}
|
||||
|
||||
public static TCPClient Create(string host, int port, bool autoConnect = true)
|
||||
{
|
||||
return new TCPClient(host, port, autoConnect);
|
||||
}
|
||||
|
||||
public static TCPClient Create(string endpoint, bool autoConnect = true)
|
||||
{
|
||||
int idx = endpoint.LastIndexOf(':');
|
||||
if (idx == -1)
|
||||
throw new ArgumentException("Invalid endpoint: " + endpoint);
|
||||
|
||||
string host = endpoint.Substring(0, idx);
|
||||
string portString = endpoint.Substring(idx + 1);
|
||||
int port = Convert.ToInt32(portString, 10);
|
||||
|
||||
return new TCPClient(host, port, autoConnect);
|
||||
}
|
||||
|
||||
//----------------[ Properties methods ]-----------------------//
|
||||
|
||||
public string Endpoint()
|
||||
{
|
||||
return dnsEndpoint.ToString();
|
||||
}
|
||||
|
||||
public ClientStatus Status()
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConnected()
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
return status == ClientStatus.Connected;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------[ Configure Operations ]-----------------------//
|
||||
public void SetConnectionConnectedDelegate(ConnectionConnectedDelegate ccd)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
connectConnectedDelegate = ccd;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetConnectionCloseDelegate(ConnectionCloseDelegate cwcd)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
connectionCloseDelegate = cwcd;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetErrorRecorder(common.ErrorRecorder recorder)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
errorRecorder = recorder;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetQuestProcessor(IQuestProcessor processor)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
questProcessor = processor;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------[ Internal Configure Operations ]-----------------------//
|
||||
private void SetClientStatus(TCPConnection conn, ClientStatus newStatus)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
if (connection == conn)
|
||||
{
|
||||
status = newStatus;
|
||||
if (status == ClientStatus.Closed)
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigConnectedDelegate(TCPConnection conn, ConnectionConnectedDelegate cb, ManualResetEvent finishEvent)
|
||||
{
|
||||
conn.SetConnectedDelegate((Int64 connectionId, string endpoint, bool connected) =>
|
||||
{
|
||||
if (!connected)
|
||||
SetClientStatus(conn, ClientStatus.Closed);
|
||||
|
||||
if (cb != null)
|
||||
try
|
||||
{
|
||||
cb(connectionId, endpoint, connected);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Connected event exception. Remote endpoint: " + endpoint + ".", ex);
|
||||
}
|
||||
|
||||
if (connected)
|
||||
SetClientStatus(conn, ClientStatus.Connected);
|
||||
|
||||
finishEvent.Set();
|
||||
});
|
||||
}
|
||||
|
||||
private void ConfigWillCloseDelegate(TCPConnection conn, ConnectionCloseDelegate cb)
|
||||
{
|
||||
conn.SetCloseDelegate((Int64 connectionId, string endpoint, bool causedByError) =>
|
||||
{
|
||||
SetClientStatus(conn, ClientStatus.Closed);
|
||||
|
||||
cb?.Invoke(connectionId, endpoint, causedByError);
|
||||
});
|
||||
}
|
||||
|
||||
//----------------[ Connect Operations ]-----------------------//
|
||||
private void RealConnect()
|
||||
{
|
||||
TCPConnection conn;
|
||||
lock (interLocker)
|
||||
{
|
||||
if (status != ClientStatus.Closed)
|
||||
return;
|
||||
|
||||
connection = new TCPConnection(dnsEndpoint);
|
||||
|
||||
ConfigConnectedDelegate(connection, connectConnectedDelegate, syncConnectingEvent);
|
||||
|
||||
ConfigWillCloseDelegate(connection, connectionCloseDelegate);
|
||||
|
||||
if (questProcessor != null)
|
||||
connection.SetQuestProcessor(questProcessor);
|
||||
|
||||
if (errorRecorder != null)
|
||||
connection.SetErrorRecorder(errorRecorder);
|
||||
|
||||
status = ClientStatus.Connecting;
|
||||
syncConnectingEvent.Reset();
|
||||
|
||||
conn = connection;
|
||||
}
|
||||
|
||||
conn.AsyncConnect(ConnectTimeout);
|
||||
}
|
||||
|
||||
public void AsyncConnect()
|
||||
{
|
||||
RealConnect();
|
||||
}
|
||||
|
||||
public bool SyncConnect()
|
||||
{
|
||||
RealConnect();
|
||||
syncConnectingEvent.WaitOne();
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
return (status == ClientStatus.Connected);
|
||||
}
|
||||
}
|
||||
|
||||
public void AsyncReconnect()
|
||||
{
|
||||
Close();
|
||||
AsyncConnect();
|
||||
}
|
||||
|
||||
public bool SyncReconnect()
|
||||
{
|
||||
Close();
|
||||
return SyncConnect();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
TCPConnection conn;
|
||||
lock (interLocker)
|
||||
{
|
||||
conn = connection;
|
||||
connection = null;
|
||||
status = ClientStatus.Closed;
|
||||
syncConnectingEvent.Set(); //-- If some threads are waiting for sync connecting finished.
|
||||
}
|
||||
|
||||
if (conn != null)
|
||||
conn.Close();
|
||||
}
|
||||
|
||||
//----------------[ Operations ]-----------------------//
|
||||
public bool SendQuest(Quest quest, IAnswerCallback callback, int timeout = 0)
|
||||
{
|
||||
if (AutoConnect)
|
||||
AsyncConnect(); //-- Auto check and reconnect if necessary.
|
||||
|
||||
TCPConnection conn = null;
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
conn = connection;
|
||||
}
|
||||
|
||||
if (conn != null)
|
||||
{
|
||||
if (timeout == 0)
|
||||
timeout = QuestTimeout;
|
||||
|
||||
if (timeout == 0)
|
||||
timeout = ClientEngine.globalQuestTimeoutSeconds;
|
||||
|
||||
conn.SendQuest(quest, callback, timeout);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SendQuest(Quest quest, AnswerDelegate callback, int timeout = 0)
|
||||
{
|
||||
AnswerDelegateCallback cb = new AnswerDelegateCallback(callback);
|
||||
return SendQuest(quest, cb, timeout);
|
||||
}
|
||||
|
||||
public Answer SendQuest(Quest quest, int timeout = 0)
|
||||
{
|
||||
if (quest.IsOneWay())
|
||||
{
|
||||
SendQuest(quest, (IAnswerCallback)null, timeout);
|
||||
return null;
|
||||
}
|
||||
|
||||
SyncAnswerCallback callback = new SyncAnswerCallback(quest);
|
||||
if (SendQuest(quest, callback, timeout))
|
||||
{
|
||||
return callback.GetAnswer();
|
||||
}
|
||||
|
||||
Answer answer = new Answer(quest);
|
||||
answer.FillErrorCode(ErrorCode.FPNN_EC_CORE_INVALID_CONNECTION);
|
||||
return answer;
|
||||
}
|
||||
|
||||
public void SendAnswer(Answer answer)
|
||||
{
|
||||
TCPConnection conn = null;
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
conn = connection;
|
||||
}
|
||||
|
||||
if (conn != null)
|
||||
conn.SendAnswer(answer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 320517d4713ce44bca818220027f7110
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,998 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using com.fpnn.proto;
|
||||
|
||||
namespace com.fpnn
|
||||
{
|
||||
internal class TCPConnection
|
||||
{
|
||||
private const int MaxRecursionDeepOfReceiveFunction = 10;
|
||||
private const int MaxRecursionDeepOfSendFunction = 10;
|
||||
|
||||
private class AnswerCallbackUnit
|
||||
{
|
||||
public IAnswerCallback callback;
|
||||
public UInt32 seqNum;
|
||||
public Int64 timeoutTime;
|
||||
}
|
||||
|
||||
//----------------[ fields ]-----------------------//
|
||||
private object interLocker;
|
||||
private readonly EndPoint endpoint;
|
||||
private TCPClient.ClientStatus status;
|
||||
private volatile bool beginClosing;
|
||||
private volatile bool requireClose;
|
||||
private bool connectingCanBeCannelled;
|
||||
private int connectCompletedSignForUnityIl2CPPDisCompliantImplement;
|
||||
|
||||
private object socketLocker;
|
||||
private Socket socket;
|
||||
private SocketAsyncEventArgs receiveAsyncEventArgs;
|
||||
private SocketAsyncEventArgs sendAsyncEventArgs;
|
||||
private int recursionDeepOfReceiveFunction;
|
||||
private int recursionDeepOfSendFunction;
|
||||
|
||||
private Int64 connectionId;
|
||||
private ConnectionConnectedDelegate connectConnectedDelegate;
|
||||
private ConnectionCloseDelegate connectionCloseDelegate;
|
||||
private IQuestProcessor questProcessor;
|
||||
|
||||
private int currSendOffset;
|
||||
private byte[] currSendBuffer;
|
||||
private Queue<byte[]> sendQueue;
|
||||
private ReceiverBase receiver;
|
||||
|
||||
private Dictionary<Int64, HashSet<AnswerCallbackUnit>> callbackTimeoutMap;
|
||||
private Dictionary<UInt32, AnswerCallbackUnit> callbackSeqNumMap;
|
||||
|
||||
private common.ErrorRecorder errorRecorder;
|
||||
|
||||
//----------------[ Constructor ]-----------------------//
|
||||
|
||||
public TCPConnection(EndPoint endpoint)
|
||||
{
|
||||
interLocker = new object();
|
||||
this.endpoint = endpoint;
|
||||
status = TCPClient.ClientStatus.Closed;
|
||||
beginClosing = false;
|
||||
requireClose = false;
|
||||
connectingCanBeCannelled = false;
|
||||
connectCompletedSignForUnityIl2CPPDisCompliantImplement = 0;
|
||||
|
||||
socketLocker = new object();
|
||||
socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
receiveAsyncEventArgs = new SocketAsyncEventArgs { RemoteEndPoint = endpoint };
|
||||
receiveAsyncEventArgs.Completed += IO_Completed;
|
||||
|
||||
currSendOffset = 0;
|
||||
currSendBuffer = null;
|
||||
sendQueue = new Queue<byte[]>();
|
||||
callbackTimeoutMap = new Dictionary<Int64, HashSet<AnswerCallbackUnit>>();
|
||||
callbackSeqNumMap = new Dictionary<UInt32, AnswerCallbackUnit>();
|
||||
}
|
||||
|
||||
//----------------[ Configure Operations ]-----------------------//
|
||||
|
||||
public void SetConnectedDelegate(ConnectionConnectedDelegate cb)
|
||||
{
|
||||
connectConnectedDelegate = cb;
|
||||
}
|
||||
|
||||
public void SetCloseDelegate(ConnectionCloseDelegate cb)
|
||||
{
|
||||
connectionCloseDelegate = cb;
|
||||
}
|
||||
|
||||
public void SetQuestProcessor(IQuestProcessor questProcessor)
|
||||
{
|
||||
this.questProcessor = questProcessor;
|
||||
}
|
||||
|
||||
public void SetErrorRecorder(common.ErrorRecorder er)
|
||||
{
|
||||
errorRecorder = er;
|
||||
}
|
||||
|
||||
//----------------[ Properties methods ]-----------------------//
|
||||
public TCPClient.ClientStatus Status()
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------[ I/O Operations ]-----------------------//
|
||||
|
||||
public void AsyncConnect(int connectTimeout)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
if (status != TCPClient.ClientStatus.Closed)
|
||||
return;
|
||||
|
||||
status = TCPClient.ClientStatus.Connecting;
|
||||
}
|
||||
|
||||
if (ClientEngine.RegisterConnectingConnection(this, connectTimeout) == false)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
status = TCPClient.ClientStatus.Closed;
|
||||
beginClosing = true;
|
||||
}
|
||||
|
||||
CallConnectionConnectedDelegate(0, false, "Connecting cannel event exception. Remote endpoint: " + endpoint + ".");
|
||||
connectionCloseDelegate = null;
|
||||
lock (socketLocker)
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
connectionId = 0;
|
||||
receiver = new StandardReceiver();
|
||||
sendAsyncEventArgs = new SocketAsyncEventArgs { RemoteEndPoint = endpoint };
|
||||
sendAsyncEventArgs.Completed += IO_Completed;
|
||||
|
||||
recursionDeepOfReceiveFunction = 0;
|
||||
recursionDeepOfSendFunction = 0;
|
||||
|
||||
try
|
||||
{
|
||||
bool status;
|
||||
lock (socketLocker)
|
||||
{
|
||||
status = socket.ConnectAsync(receiveAsyncEventArgs);
|
||||
}
|
||||
if (!status) //-- Synchronous
|
||||
ConnectCompleted(socket, receiveAsyncEventArgs);
|
||||
else
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
if (requireClose)
|
||||
CannelConnecting();
|
||||
else
|
||||
connectingCanBeCannelled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Connect to " + endpoint + " failed.", e);
|
||||
|
||||
CloseWhenConnectingError();
|
||||
}
|
||||
catch (ObjectDisposedException e)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Connect to " + endpoint + " failed.", e);
|
||||
|
||||
CloseWhenConnectingError();
|
||||
}
|
||||
}
|
||||
|
||||
void IO_Completed(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
switch (e.LastOperation)
|
||||
{
|
||||
case SocketAsyncOperation.Receive:
|
||||
recursionDeepOfReceiveFunction = 0;
|
||||
ReceiveCompleted(sender, e);
|
||||
break;
|
||||
|
||||
case SocketAsyncOperation.Send:
|
||||
recursionDeepOfSendFunction = 0;
|
||||
SendCompleted(sender, e);
|
||||
break;
|
||||
|
||||
case SocketAsyncOperation.Connect:
|
||||
ConnectCompleted(sender, e);
|
||||
break;
|
||||
|
||||
case SocketAsyncOperation.Disconnect:
|
||||
FinallyShutdownClose();
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
string info;
|
||||
if (e == receiveAsyncEventArgs)
|
||||
info = "receiveAsyncEventArgs.";
|
||||
else if (e == sendAsyncEventArgs)
|
||||
info = "sendAsyncEventArgs.";
|
||||
else
|
||||
info = "closeAsyncEventArgs or other.";
|
||||
|
||||
CloseByException("IO_Completed exception. LastOperation is " + e.LastOperation
|
||||
+ ". Error SocketAsyncEventArgs is " + info, null, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectCompleted(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
int unityDisCompliantSign = System.Threading.Interlocked.Exchange(ref connectCompletedSignForUnityIl2CPPDisCompliantImplement, 1);
|
||||
if (unityDisCompliantSign != 0)
|
||||
{
|
||||
if (e.SocketError != SocketError.Success)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
if (status == TCPClient.ClientStatus.Connecting)
|
||||
{
|
||||
requireClose = true;
|
||||
return;
|
||||
}
|
||||
else if (status == TCPClient.ClientStatus.Closed)
|
||||
return;
|
||||
}
|
||||
|
||||
//-- status == TCPClient.ClientStatus.Connected
|
||||
Close();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.SocketError != SocketError.Success)
|
||||
{
|
||||
if (errorRecorder != null && !requireClose)
|
||||
errorRecorder.RecordError("Connect to " + endpoint + " failed. Due to SocketError: " + e.SocketError);
|
||||
|
||||
CloseWhenConnectingError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (requireClose)
|
||||
{
|
||||
CallConnectionConnectedDelegate(0, false, "Connecting cannel event exception. Remote endpoint: " + endpoint + ".");
|
||||
connectionCloseDelegate = null;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
receiveAsyncEventArgs.SetBuffer(receiver.buffer, receiver.offset, receiver.requireLength - receiver.offset);
|
||||
lock (socketLocker)
|
||||
{
|
||||
connectionId = socket.Handle.ToInt64();
|
||||
}
|
||||
|
||||
CallConnectionConnectedDelegate(connectionId, true, "Connected event exception. Remote endpoint: " + endpoint + ".");
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
status = TCPClient.ClientStatus.Connected;
|
||||
}
|
||||
|
||||
if (requireClose)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClientEngine.RegisterConnectedConnection(this) == false)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
CheckSending();
|
||||
|
||||
try
|
||||
{
|
||||
bool status;
|
||||
lock (socketLocker)
|
||||
{
|
||||
status = socket.ReceiveAsync(receiveAsyncEventArgs);
|
||||
}
|
||||
if (!status)
|
||||
ReceiveCompleted(socket, receiveAsyncEventArgs);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
CloseByException("Receive data from " + endpoint + " exception. Connection is broken.", ex, true);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
CloseByException("Receive data from " + endpoint + " exception. Access socket is error.", ex, false);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//-- Do nothings
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
if (e.SocketError != SocketError.Success && !beginClosing)
|
||||
{
|
||||
CloseByException("Receive data from " + endpoint + " failed. Due to SocketError: " + e.SocketError, null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.BytesTransferred == 0)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
receiver.offset += e.BytesTransferred;
|
||||
if (receiver.offset == receiver.requireLength)
|
||||
{
|
||||
Quest quest;
|
||||
Answer answer;
|
||||
|
||||
try
|
||||
{
|
||||
receiver.Done(out quest, out answer);
|
||||
}
|
||||
catch (ReceiverErrorMessageException ex)
|
||||
{
|
||||
CloseByException("Processing received data from " + endpoint + " error: " + ex.Message + ". Connection will be closed.", null, false);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CloseByException("Processing received data from " + endpoint + " exception. Connection will be closed.", ex, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (answer != null)
|
||||
DealAnswer(answer);
|
||||
else if (quest != null)
|
||||
DealQuest(quest);
|
||||
}
|
||||
|
||||
if (beginClosing)
|
||||
return;
|
||||
|
||||
receiveAsyncEventArgs.SetBuffer(receiver.buffer, receiver.offset, receiver.requireLength - receiver.offset);
|
||||
|
||||
try
|
||||
{
|
||||
bool status;
|
||||
lock (socketLocker)
|
||||
{
|
||||
status = socket.ReceiveAsync(receiveAsyncEventArgs);
|
||||
}
|
||||
if (!status)
|
||||
{
|
||||
recursionDeepOfReceiveFunction += 1;
|
||||
if (recursionDeepOfReceiveFunction <= MaxRecursionDeepOfReceiveFunction)
|
||||
{
|
||||
ReceiveCompleted(socket, receiveAsyncEventArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
recursionDeepOfReceiveFunction = 0;
|
||||
ClientEngine.RunTask(() => {
|
||||
ReceiveCompleted(socket, receiveAsyncEventArgs);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
CloseByException("Receive data from " + endpoint + " exception. Connection is broken.", ex, true);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
CloseByException("Receive data from " + endpoint + " exception. Access socket is error.", ex, false);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//-- Do nothings
|
||||
}
|
||||
}
|
||||
|
||||
private void SendCompleted(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
if (e.SocketError != SocketError.Success && !beginClosing)
|
||||
{
|
||||
CloseByException("Send data to " + endpoint + " failed. Due to SocketError: " + e.SocketError, null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
currSendOffset += e.BytesTransferred;
|
||||
if (currSendOffset == currSendBuffer.Length)
|
||||
{
|
||||
currSendOffset = 0;
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
if (sendQueue.Count > 0)
|
||||
{
|
||||
currSendBuffer = sendQueue.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
currSendBuffer = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (beginClosing)
|
||||
return;
|
||||
|
||||
sendAsyncEventArgs.SetBuffer(currSendBuffer, currSendOffset, currSendBuffer.Length - currSendOffset);
|
||||
|
||||
try
|
||||
{
|
||||
bool status;
|
||||
lock (socketLocker)
|
||||
{
|
||||
status = socket.SendAsync(sendAsyncEventArgs);
|
||||
}
|
||||
if (!status)
|
||||
{
|
||||
recursionDeepOfSendFunction += 1;
|
||||
if (recursionDeepOfSendFunction <= MaxRecursionDeepOfSendFunction)
|
||||
{
|
||||
SendCompleted(socket, sendAsyncEventArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
recursionDeepOfSendFunction = 0;
|
||||
ClientEngine.RunTask(() => {
|
||||
SendCompleted(socket, sendAsyncEventArgs);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
CloseByException("Send data to " + endpoint + " exception. Connection is broken.", ex, true);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
CloseByException("Send data to " + endpoint + " exception. Access socket is error.", ex, false);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//-- DO nothings
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckSending()
|
||||
{
|
||||
bool startSending = false;
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
if (currSendBuffer != null)
|
||||
return;
|
||||
|
||||
if (sendQueue.Count == 0)
|
||||
return;
|
||||
|
||||
startSending = true;
|
||||
currSendBuffer = sendQueue.Dequeue();
|
||||
}
|
||||
|
||||
if (!startSending)
|
||||
return;
|
||||
|
||||
if (beginClosing)
|
||||
return;
|
||||
|
||||
currSendOffset = 0;
|
||||
sendAsyncEventArgs.SetBuffer(currSendBuffer, 0, currSendBuffer.Length);
|
||||
try
|
||||
{
|
||||
bool status;
|
||||
lock (socketLocker)
|
||||
{
|
||||
status = socket.SendAsync(sendAsyncEventArgs);
|
||||
}
|
||||
if (!status)
|
||||
SendCompleted(socket, sendAsyncEventArgs);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
CloseByException("Send data to " + endpoint + " exception. Connection is broken.", ex, true);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
CloseByException("Send data to " + endpoint + " exception. Access socket is error.", ex, false);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//-- Do nothings
|
||||
}
|
||||
}
|
||||
|
||||
//----------------[ Closing Operations ]-----------------------//
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
|
||||
private void Close(bool socketDisposed)
|
||||
{
|
||||
if (beginClosing)
|
||||
return;
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
if (status == TCPClient.ClientStatus.Closed)
|
||||
return;
|
||||
|
||||
if (status == TCPClient.ClientStatus.Connecting)
|
||||
{
|
||||
requireClose = true;
|
||||
|
||||
if (connectingCanBeCannelled)
|
||||
CannelConnecting();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
status = TCPClient.ClientStatus.Closed;
|
||||
beginClosing = true;
|
||||
}
|
||||
|
||||
InternalClose(true, false, socketDisposed, false);
|
||||
}
|
||||
|
||||
private void CloseByException(string message, Exception ex, bool socketDisposed)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
{
|
||||
if (ex != null)
|
||||
errorRecorder.RecordError(message, ex);
|
||||
else
|
||||
errorRecorder.RecordError(message);
|
||||
}
|
||||
|
||||
InternalClose(true, true, socketDisposed, true);
|
||||
}
|
||||
|
||||
private void InternalClose(bool callCloseEvent, bool causedByError, bool socketDisposed, bool checkReClosing)
|
||||
{
|
||||
if (checkReClosing)
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
if (status == TCPClient.ClientStatus.Closed)
|
||||
return;
|
||||
|
||||
status = TCPClient.ClientStatus.Closed;
|
||||
}
|
||||
}
|
||||
|
||||
CleanForClose(callCloseEvent, causedByError);
|
||||
|
||||
if (socketDisposed)
|
||||
{
|
||||
lock (socketLocker)
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SocketAsyncEventArgs closeAsyncEventArgs = new SocketAsyncEventArgs { RemoteEndPoint = endpoint };
|
||||
closeAsyncEventArgs.Completed += IO_Completed;
|
||||
|
||||
try
|
||||
{
|
||||
bool status;
|
||||
lock (socketLocker)
|
||||
{
|
||||
status = socket.DisconnectAsync(closeAsyncEventArgs);
|
||||
}
|
||||
if (!status)
|
||||
FinallyShutdownClose();
|
||||
|
||||
return;
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Internal closing failed. SocketException means accessing socket is failed.", e);
|
||||
|
||||
lock (socketLocker)
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
lock (socketLocker)
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanForClose(bool callCloseEvent, bool causedByError)
|
||||
{
|
||||
ClientEngine.UnregisterConnection(this);
|
||||
|
||||
ClearAllCallback(ErrorCode.FPNN_EC_CORE_CONNECTION_CLOSED);
|
||||
|
||||
if (callCloseEvent && connectionCloseDelegate != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
connectionCloseDelegate(connectionId, endpoint.ToString(), causedByError);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Close event exception. Remote endpoint: " + endpoint + ".", ex);
|
||||
}
|
||||
}
|
||||
connectionCloseDelegate = null;
|
||||
}
|
||||
|
||||
private void CannelConnecting()
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket.CancelConnectAsync(receiveAsyncEventArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Cannel connecting exception. Remote endpoint: " + endpoint + ".", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseWhenConnectingError()
|
||||
{
|
||||
ClientEngine.UnregisterConnection(this);
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
if (status == TCPClient.ClientStatus.Closed)
|
||||
return;
|
||||
|
||||
status = TCPClient.ClientStatus.Closed;
|
||||
beginClosing = true;
|
||||
}
|
||||
|
||||
CallConnectionConnectedDelegate(0, false, "Connecting failed event exception. Remote endpoint: " + endpoint + ".");
|
||||
connectionCloseDelegate = null;
|
||||
|
||||
ClearAllCallback(ErrorCode.FPNN_EC_CORE_INVALID_CONNECTION);
|
||||
|
||||
lock (socketLocker)
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void FinallyShutdownClose()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (socketLocker)
|
||||
{
|
||||
if (socket.Connected)
|
||||
socket.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{ /* Do nothing. */ }
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Exception when socket.Shutdown() action.", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (socketLocker)
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------[ callbacks Functions ]-----------------------//
|
||||
|
||||
private void CallConnectionConnectedDelegate(Int64 connectionId, bool connected, string exceptionMessage)
|
||||
{
|
||||
if (connectConnectedDelegate != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
connectConnectedDelegate(connectionId, endpoint.ToString(), connected);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError(exceptionMessage, ex);
|
||||
}
|
||||
|
||||
connectConnectedDelegate = null;
|
||||
}
|
||||
}
|
||||
|
||||
static void RunCallback(IAnswerCallback callback, int errorCode)
|
||||
{
|
||||
ClientEngine.RunTask(() => {
|
||||
callback.OnException(null, errorCode);
|
||||
});
|
||||
}
|
||||
|
||||
public void CleanTimeoutedCallbacks(Int64 currentSeconds)
|
||||
{
|
||||
HashSet<IAnswerCallback> callbacks = new HashSet<IAnswerCallback>();
|
||||
Dictionary<Int64, HashSet<AnswerCallbackUnit>> timeoutedDict = new Dictionary<Int64, HashSet<AnswerCallbackUnit>>();
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
foreach (KeyValuePair<Int64, HashSet<AnswerCallbackUnit>> kvp in callbackTimeoutMap)
|
||||
{
|
||||
if (kvp.Key <= currentSeconds)
|
||||
timeoutedDict.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Int64, HashSet<AnswerCallbackUnit>> kvp in timeoutedDict)
|
||||
{
|
||||
callbackTimeoutMap.Remove(kvp.Key);
|
||||
|
||||
foreach(AnswerCallbackUnit unit in kvp.Value)
|
||||
{
|
||||
callbackSeqNumMap.Remove(unit.seqNum);
|
||||
callbacks.Add(unit.callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (IAnswerCallback callback in callbacks)
|
||||
RunCallback(callback, ErrorCode.FPNN_EC_CORE_TIMEOUT);
|
||||
}
|
||||
|
||||
private void ClearAllCallback(int errorCode)
|
||||
{
|
||||
Dictionary<UInt32, AnswerCallbackUnit> oldCallbackDict = new Dictionary<UInt32, AnswerCallbackUnit>();
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
Dictionary<UInt32, AnswerCallbackUnit> tmp = callbackSeqNumMap;
|
||||
callbackSeqNumMap = oldCallbackDict;
|
||||
oldCallbackDict = tmp;
|
||||
|
||||
callbackTimeoutMap.Clear();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<UInt32, AnswerCallbackUnit> kvp in oldCallbackDict)
|
||||
RunCallback(kvp.Value.callback, errorCode);
|
||||
}
|
||||
|
||||
//----------------[ Quest & Answer Processing ]-----------------------//
|
||||
|
||||
private void RunQuestProcessor(Quest quest, QuestProcessDelegate process)
|
||||
{
|
||||
TCPConnection conn = this;
|
||||
|
||||
ClientEngine.RunTask(() => {
|
||||
|
||||
Answer answer = null;
|
||||
bool asyncAnswered = false;
|
||||
AdvancedAnswerInfo.Reset(conn, quest);
|
||||
|
||||
try
|
||||
{
|
||||
answer = process(connectionId, endpoint.ToString(), quest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Run quest process for method: " + quest.Method(), ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
asyncAnswered = AdvancedAnswerInfo.Answered();
|
||||
}
|
||||
|
||||
if (quest.IsTwoWay() && !asyncAnswered)
|
||||
{
|
||||
if (answer == null)
|
||||
{
|
||||
answer = new Answer(quest);
|
||||
answer.FillErrorInfo(ErrorCode.FPNN_EC_CORE_UNKNOWN_ERROR, "Two way quest " + quest.Method() + " lose an answer.");
|
||||
}
|
||||
SendAnswer(answer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (answer != null)
|
||||
if (errorRecorder != null)
|
||||
{
|
||||
if (quest.IsOneWay())
|
||||
errorRecorder.RecordError("Answer created for one way quest: " + quest.Method());
|
||||
else
|
||||
errorRecorder.RecordError("Answer created reduplicated for two way quest: " + quest.Method());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void DealQuest(Quest quest)
|
||||
{
|
||||
if (questProcessor != null)
|
||||
{
|
||||
QuestProcessDelegate process = questProcessor.GetQuestProcessDelegate(quest.Method());
|
||||
if (process != null)
|
||||
{
|
||||
RunQuestProcessor(quest, process);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (quest.IsTwoWay())
|
||||
{
|
||||
Answer answer = new Answer(quest);
|
||||
answer.FillErrorInfo(ErrorCode.FPNN_EC_CORE_UNKNOWN_METHOD, "This method is not supported by client.");
|
||||
SendAnswer(answer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (quest.IsTwoWay())
|
||||
{
|
||||
Answer answer = new Answer(quest);
|
||||
answer.FillErrorInfo(ErrorCode.FPNN_EC_CORE_INVALID_PACKAGE, "Client without quest processor.");
|
||||
SendAnswer(answer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DealAnswer(Answer answer)
|
||||
{
|
||||
AnswerCallbackUnit unit = null;
|
||||
UInt32 seq = answer.SeqNum();
|
||||
lock (interLocker)
|
||||
{
|
||||
if (callbackSeqNumMap.TryGetValue(seq, out unit))
|
||||
{
|
||||
callbackSeqNumMap.Remove(seq);
|
||||
|
||||
if (callbackTimeoutMap.TryGetValue(unit.timeoutTime, out HashSet<AnswerCallbackUnit> cbSet))
|
||||
{
|
||||
cbSet.Remove(unit);
|
||||
if (cbSet.Count == 0)
|
||||
callbackTimeoutMap.Remove(unit.timeoutTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unit != null)
|
||||
{
|
||||
ClientEngine.RunTask(() => {
|
||||
unit.callback.OnAnswer(answer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//private void sendQuest(Quest quest, AnswerCallback callback, int timeoutInSeconds, boolean keyExchangedQuest)
|
||||
|
||||
public void SendQuest(Quest quest, IAnswerCallback callback, int timeoutInSeconds)
|
||||
{
|
||||
if (quest == null)
|
||||
{
|
||||
if (callback != null)
|
||||
RunCallback(callback, ErrorCode.FPNN_EC_CORE_INVALID_PACKAGE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool isClosed;
|
||||
byte[] raw;
|
||||
try
|
||||
{
|
||||
raw = quest.Raw();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (callback != null)
|
||||
RunCallback(callback, ErrorCode.FPNN_EC_PROTO_UNKNOWN_ERROR);
|
||||
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Send quest cannelled. Quest.Raw() exception.", ex);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
isClosed = (status == TCPClient.ClientStatus.Closed);
|
||||
if (!isClosed)
|
||||
{
|
||||
sendQueue.Enqueue(raw);
|
||||
|
||||
if (callback != null)
|
||||
{
|
||||
if (timeoutInSeconds == 0)
|
||||
timeoutInSeconds = ClientEngine.globalQuestTimeoutSeconds;
|
||||
|
||||
TimeSpan span = DateTime.UtcNow - ClientEngine.originDateTime;
|
||||
Int64 seconds = (Int64)Math.Floor(span.TotalSeconds) + timeoutInSeconds;
|
||||
|
||||
AnswerCallbackUnit unit = new AnswerCallbackUnit();
|
||||
unit.callback = callback;
|
||||
unit.seqNum = quest.SeqNum();
|
||||
unit.timeoutTime = seconds;
|
||||
|
||||
callbackSeqNumMap.Add(quest.SeqNum(), unit);
|
||||
|
||||
if (callbackTimeoutMap.TryGetValue(seconds, out HashSet<AnswerCallbackUnit> cbSet))
|
||||
{
|
||||
cbSet.Add(unit);
|
||||
}
|
||||
else
|
||||
{
|
||||
cbSet = new HashSet<AnswerCallbackUnit>();
|
||||
cbSet.Add(unit);
|
||||
callbackTimeoutMap.Add(seconds, cbSet);
|
||||
}
|
||||
}
|
||||
|
||||
if (status == TCPClient.ClientStatus.Connecting)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isClosed)
|
||||
CheckSending();
|
||||
else
|
||||
{
|
||||
if (callback != null)
|
||||
RunCallback(callback, ErrorCode.FPNN_EC_CORE_INVALID_CONNECTION);
|
||||
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Send Quest " + quest.Method() + " on closed connection.");
|
||||
}
|
||||
}
|
||||
|
||||
public void SendAnswer(Answer answer)
|
||||
{
|
||||
bool checkSending = true;
|
||||
byte[] raw;
|
||||
|
||||
try
|
||||
{
|
||||
raw = answer.Raw();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Send answer cannelled. Answer.Raw() exception.", ex);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (interLocker)
|
||||
{
|
||||
if (status == TCPClient.ClientStatus.Connected)
|
||||
{
|
||||
sendQueue.Enqueue(raw);
|
||||
}
|
||||
else
|
||||
checkSending = false;
|
||||
}
|
||||
|
||||
if (checkSending)
|
||||
CheckSending();
|
||||
else
|
||||
{
|
||||
if (errorRecorder != null)
|
||||
errorRecorder.RecordError("Send answer on closed connection.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1e915f7872174de6bcc1cdc8208d02c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fadd430f72bbc48de8cae11f2b79b9a1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
#if UNITY_2017_1_OR_NEWER
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace com.fpnn
|
||||
{
|
||||
public static partial class ClientEngine
|
||||
{
|
||||
static partial void PlatformInit()
|
||||
{
|
||||
ConnectionMonitor.Instance.Init();
|
||||
//Application.quitting += () => {
|
||||
// ClientEngine.Close();
|
||||
//};
|
||||
}
|
||||
|
||||
static partial void PlatformUninit()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class ConnectionMonitor : Singleton<ConnectionMonitor>
|
||||
{
|
||||
private bool _isPause;
|
||||
private bool _isFocus;
|
||||
private bool _isBackground;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
this._isPause = false;
|
||||
this._isFocus = true;
|
||||
this._isBackground = false;
|
||||
}
|
||||
|
||||
public void Init() { }
|
||||
|
||||
private void CheckInBackground()
|
||||
{
|
||||
if (_isPause && !_isFocus)
|
||||
{
|
||||
if (_isBackground == false)
|
||||
{
|
||||
_isBackground = true;
|
||||
|
||||
#if UNITY_IOS
|
||||
if (ClientEngine.closeConnectionsWhenBackground)
|
||||
{
|
||||
ClientEngine.ChangeForbiddenRegisterConnection(_isBackground);
|
||||
ClientEngine.StopAllConnections();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isBackground)
|
||||
{
|
||||
_isBackground = false;
|
||||
|
||||
#if UNITY_IOS
|
||||
if (ClientEngine.closeConnectionsWhenBackground)
|
||||
{
|
||||
ClientEngine.ChangeForbiddenRegisterConnection(_isBackground);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnApplicationPause(bool pauseStatus)
|
||||
{
|
||||
_isPause = pauseStatus;
|
||||
|
||||
CheckInBackground();
|
||||
}
|
||||
|
||||
void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
_isFocus = hasFocus;
|
||||
|
||||
CheckInBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a41e8ca65b9e3415f9a4b0b2407a0aca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
#if UNITY_2017_1_OR_NEWER
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace com.fpnn {
|
||||
|
||||
public class Singleton<T> : MonoBehaviour where T : Singleton<T> {
|
||||
|
||||
public static T Instance {
|
||||
get {
|
||||
if (instance == null) {
|
||||
#if UNITY_EDITOR
|
||||
T[] managers = Object.FindObjectsOfType(typeof(T)) as T[];
|
||||
|
||||
if (managers.Length != 0) {
|
||||
if (managers.Length == 1) {
|
||||
instance = managers[0];
|
||||
instance.gameObject.name = typeof(T).Name;
|
||||
return instance;
|
||||
} else {
|
||||
Debug.LogError("Class " + typeof(T).Name + " exists multiple times in violation of singleton pattern. Destroying all copies");
|
||||
|
||||
foreach (T manager in managers) {
|
||||
Destroy(manager.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var go = new GameObject(typeof(T).Name, typeof(T));
|
||||
instance = go.GetComponent<T>();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
set {
|
||||
instance = value as T;
|
||||
}
|
||||
}
|
||||
|
||||
private static T instance;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69a9d33ada7b746d383d500683ccea60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe1ce30ab96834f75b384c6f95ce8513
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
namespace com.fpnn.common
|
||||
{
|
||||
public interface ErrorRecorder
|
||||
{
|
||||
void RecordError(Exception e);
|
||||
void RecordError(string message);
|
||||
void RecordError(string message, Exception e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3eb5149f8432d46c9b5fd967c04837cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,640 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace com.fpnn.common
|
||||
{
|
||||
public static class Json
|
||||
{
|
||||
public static object Parse(string json)
|
||||
{
|
||||
JsonParser parser = JsonParser.Create(json);
|
||||
return parser.Parse();
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> ParseObject(string json)
|
||||
{
|
||||
JsonParser parser = JsonParser.Create(json);
|
||||
return parser.ParseObject();
|
||||
}
|
||||
|
||||
public static string ToString(object obj)
|
||||
{
|
||||
JsonStringify js = new JsonStringify();
|
||||
return js.Stringify(obj);
|
||||
}
|
||||
}
|
||||
|
||||
//============================[ Exception ]============================//
|
||||
public class JsonException : Exception
|
||||
{
|
||||
public JsonException(string message) : base(message) { }
|
||||
public JsonException(String message, Exception ex) : base(message, ex) { }
|
||||
}
|
||||
|
||||
public class InvalidJsonException : JsonException
|
||||
{
|
||||
public InvalidJsonException(string message) : base(message) { }
|
||||
public InvalidJsonException(string message, Exception ex) : base(message, ex) { }
|
||||
}
|
||||
|
||||
public class JsonTypeException : JsonException
|
||||
{
|
||||
public JsonTypeException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
//============================[ Json Parser ]============================//
|
||||
|
||||
internal enum JsonElementType
|
||||
{
|
||||
Empty,
|
||||
Null,
|
||||
Boolean,
|
||||
String,
|
||||
Int64,
|
||||
UInt64,
|
||||
Double,
|
||||
Array,
|
||||
Dictionary,
|
||||
}
|
||||
|
||||
internal delegate void JsonParseSignDelegate(JsonParser parser);
|
||||
|
||||
internal class JsonParser
|
||||
{
|
||||
class JsonElement
|
||||
{
|
||||
public object element;
|
||||
public JsonElementType type;
|
||||
}
|
||||
|
||||
static private readonly Dictionary<char, JsonParseSignDelegate> signDelegateMap;
|
||||
static private readonly Dictionary<char, ushort> hexTable;
|
||||
static private readonly HashSet<char> numericalChars;
|
||||
|
||||
private readonly string json;
|
||||
private int idx;
|
||||
private string key;
|
||||
private bool wantKey;
|
||||
private bool wantValue;
|
||||
private bool wantComma;
|
||||
private bool wantSemicolon;
|
||||
|
||||
private readonly Stack<JsonElement> elementStack;
|
||||
|
||||
private object parseResult;
|
||||
private JsonElementType resultType;
|
||||
|
||||
static JsonParser()
|
||||
{
|
||||
signDelegateMap = new Dictionary<char, JsonParseSignDelegate>
|
||||
{
|
||||
{ '{', EnterObject },
|
||||
{ '}', ExitObject },
|
||||
{ '[', EnterArray },
|
||||
{ ']', ExitArray },
|
||||
{ ',', ProcessComma },
|
||||
{ ':', ProcessSemicolon },
|
||||
{ '"', ProcessString },
|
||||
};
|
||||
|
||||
hexTable = new Dictionary<char, ushort>
|
||||
{
|
||||
{ '0', 0 },
|
||||
{ '1', 1 },
|
||||
{ '2', 2 },
|
||||
{ '3', 3 },
|
||||
{ '4', 4 },
|
||||
{ '5', 5 },
|
||||
{ '6', 6 },
|
||||
{ '7', 7 },
|
||||
{ '8', 8 },
|
||||
{ '9', 9 },
|
||||
|
||||
{ 'a', 10 },
|
||||
{ 'b', 11 },
|
||||
{ 'c', 12 },
|
||||
{ 'd', 13 },
|
||||
{ 'e', 14 },
|
||||
{ 'f', 15 },
|
||||
|
||||
{ 'A', 10 },
|
||||
{ 'B', 11 },
|
||||
{ 'C', 12 },
|
||||
{ 'D', 13 },
|
||||
{ 'E', 14 },
|
||||
{ 'F', 15 },
|
||||
};
|
||||
|
||||
numericalChars = new HashSet<char>
|
||||
{
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'+', '-', '.', 'E', 'e'
|
||||
};
|
||||
}
|
||||
|
||||
private static void EnterObject(JsonParser parser)
|
||||
{
|
||||
parser.EnterObject();
|
||||
}
|
||||
private static void ExitObject(JsonParser parser)
|
||||
{
|
||||
parser.ExitObject();
|
||||
}
|
||||
private static void EnterArray(JsonParser parser)
|
||||
{
|
||||
parser.EnterArray();
|
||||
}
|
||||
private static void ExitArray(JsonParser parser)
|
||||
{
|
||||
parser.ExitArray();
|
||||
}
|
||||
private static void ProcessComma(JsonParser parser)
|
||||
{
|
||||
parser.ProcessComma();
|
||||
}
|
||||
private static void ProcessSemicolon(JsonParser parser)
|
||||
{
|
||||
parser.ProcessSemicolon();
|
||||
}
|
||||
private static void ProcessString(JsonParser parser)
|
||||
{
|
||||
parser.ProcessString();
|
||||
}
|
||||
|
||||
public static JsonParser Create(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
throw new InvalidJsonException("Json Parser: content error. Null or empty content.");
|
||||
|
||||
JsonParser parser;
|
||||
|
||||
try
|
||||
{
|
||||
string normalizedString = json;
|
||||
if (!json.IsNormalized())
|
||||
normalizedString = json.Normalize();
|
||||
|
||||
parser = new JsonParser(normalizedString);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: content error. Include invalid Unicode.", e);
|
||||
}
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
//---------------------[ Instance Parts ]--------------------------//
|
||||
private JsonParser(string jsonString)
|
||||
{
|
||||
json = jsonString;
|
||||
idx = 0;
|
||||
|
||||
elementStack = new Stack<JsonElement>();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
key = string.Empty;
|
||||
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = false;
|
||||
wantSemicolon = false;
|
||||
|
||||
elementStack.Clear();
|
||||
|
||||
parseResult = null;
|
||||
resultType = JsonElementType.Empty;
|
||||
}
|
||||
|
||||
private void ParseCore()
|
||||
{
|
||||
Reset();
|
||||
|
||||
while (idx < json.Length && parseResult == null)
|
||||
{
|
||||
if (Char.IsWhiteSpace(json[idx]))
|
||||
{
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (signDelegateMap.TryGetValue(json[idx], out JsonParseSignDelegate method))
|
||||
method(this);
|
||||
else
|
||||
GeneralProcess();
|
||||
}
|
||||
}
|
||||
|
||||
public object Parse()
|
||||
{
|
||||
ParseCore();
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
|
||||
public Dictionary<string, object> ParseObject()
|
||||
{
|
||||
ParseCore();
|
||||
if (resultType == JsonElementType.Dictionary)
|
||||
return (Dictionary<string, object>)parseResult;
|
||||
|
||||
throw new JsonTypeException("Json Parser: want the Dictionary type, but the parsed type is " + resultType.ToString("G") + ".");
|
||||
}
|
||||
|
||||
private void FillKey(string value)
|
||||
{
|
||||
key = value;
|
||||
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = false;
|
||||
wantSemicolon = true;
|
||||
}
|
||||
|
||||
private void FillValue(object obj, JsonElementType type)
|
||||
{
|
||||
Dictionary<string, object> dict = (Dictionary<string, object>)elementStack.Peek().element;
|
||||
try
|
||||
{
|
||||
dict.Add(key, obj);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: reduplicated key: \"" + key + "\", offset " + idx, e);
|
||||
}
|
||||
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = true;
|
||||
wantSemicolon = false;
|
||||
}
|
||||
|
||||
private void InsertNode(object obj, JsonElementType type)
|
||||
{
|
||||
int stackCount = elementStack.Count;
|
||||
if (stackCount > 0)
|
||||
{
|
||||
if (wantValue)
|
||||
FillValue(obj, type);
|
||||
else
|
||||
{
|
||||
List<object> list = (List<object>)elementStack.Peek().element;
|
||||
list.Add(obj);
|
||||
|
||||
wantComma = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == JsonElementType.Array || type == JsonElementType.Dictionary)
|
||||
{
|
||||
elementStack.Push(new JsonElement()
|
||||
{
|
||||
element = obj,
|
||||
type = type
|
||||
});
|
||||
}
|
||||
else if (stackCount == 0)
|
||||
{
|
||||
parseResult = obj;
|
||||
resultType = type;
|
||||
}
|
||||
}
|
||||
|
||||
private void FinishNode()
|
||||
{
|
||||
JsonElement element = elementStack.Pop();
|
||||
|
||||
if (elementStack.Count == 0)
|
||||
{
|
||||
parseResult = element.element;
|
||||
resultType = element.type;
|
||||
}
|
||||
else
|
||||
{
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = true;
|
||||
wantSemicolon = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSlash(ref StringBuilder stringBuilder)
|
||||
{
|
||||
idx++;
|
||||
if (idx >= json.Length)
|
||||
throw new InvalidJsonException("Json Parser: content error, json truncated after '\\'.");
|
||||
|
||||
switch (json[idx])
|
||||
{
|
||||
case '"':
|
||||
stringBuilder.Append('"');
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
stringBuilder.Append('\\');
|
||||
break;
|
||||
|
||||
case '/':
|
||||
stringBuilder.Append('/');
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
stringBuilder.Append('\b');
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
stringBuilder.Append('\f');
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
stringBuilder.Append('\n');
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
stringBuilder.Append('\r');
|
||||
break;
|
||||
|
||||
case 't':
|
||||
stringBuilder.Append('\t');
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
if (idx + 5 >= json.Length)
|
||||
throw new InvalidJsonException("Json Parser: content error, json truncated after '\\u'.");
|
||||
|
||||
idx++;
|
||||
ushort value = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (hexTable.TryGetValue(json[idx+i], out ushort v))
|
||||
{
|
||||
value <<= 4;
|
||||
value += v;
|
||||
}
|
||||
else
|
||||
throw new InvalidJsonException("Json Parser: content error, invalid hex number for '\\u'. Offset " + idx);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
char c = Convert.ToChar(value);
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
catch (OverflowException e)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: content error, invalid unicode value for '\\u'. Offset " + idx, e);
|
||||
}
|
||||
|
||||
idx += 4;
|
||||
return;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
private string FetchString()
|
||||
{
|
||||
idx++;
|
||||
int startIdx = idx;
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
while (idx < json.Length)
|
||||
{
|
||||
if (json[idx] == '\\')
|
||||
{
|
||||
int count = idx - startIdx;
|
||||
if (count > 0)
|
||||
stringBuilder.Append(json, startIdx, count);
|
||||
|
||||
ProcessSlash(ref stringBuilder);
|
||||
startIdx = idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (json[idx] == '"')
|
||||
{
|
||||
int count = idx - startIdx;
|
||||
if (count > 0)
|
||||
stringBuilder.Append(json, startIdx, count);
|
||||
|
||||
idx++;
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
throw new InvalidJsonException("Json Parser: content error, json truncated in string value.");
|
||||
}
|
||||
|
||||
private void EnterObject()
|
||||
{
|
||||
if (wantKey || wantSemicolon || wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error, '{' at improper place. Offset " + idx);
|
||||
|
||||
InsertNode(new Dictionary<string, object>(), JsonElementType.Dictionary);
|
||||
|
||||
wantKey = true;
|
||||
wantValue = false;
|
||||
wantComma = false;
|
||||
wantSemicolon = false;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
private void ExitObject()
|
||||
{
|
||||
if (wantValue || wantSemicolon || elementStack.Count == 0 || elementStack.Peek().type != JsonElementType.Dictionary)
|
||||
throw new InvalidJsonException("Json Parser: content error, '}' at improper place. Offset " + idx);
|
||||
|
||||
idx++;
|
||||
FinishNode();
|
||||
}
|
||||
|
||||
private void EnterArray()
|
||||
{
|
||||
if (wantKey || wantSemicolon || wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error, '[' at improper place. Offset " + idx);
|
||||
|
||||
InsertNode(new List<object>(), JsonElementType.Array);
|
||||
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = false;
|
||||
wantSemicolon = false;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
private void ExitArray()
|
||||
{
|
||||
if (wantValue || wantSemicolon || elementStack.Count == 0 || elementStack.Peek().type != JsonElementType.Array)
|
||||
throw new InvalidJsonException("Json Parser: content error, ']' at improper place. Offset " + idx);
|
||||
|
||||
idx++;
|
||||
FinishNode();
|
||||
}
|
||||
|
||||
private void ProcessComma()
|
||||
{
|
||||
if (!wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error. ',' at error position. Offset " + idx);
|
||||
|
||||
wantComma = false;
|
||||
if (elementStack.Peek().type == JsonElementType.Dictionary)
|
||||
wantKey = true;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
private void ProcessSemicolon()
|
||||
{
|
||||
if (!wantSemicolon)
|
||||
throw new InvalidJsonException("Json Parser: content error. ':' at error position. Offset " + idx);
|
||||
|
||||
wantSemicolon = false;
|
||||
wantValue = true;
|
||||
idx++;
|
||||
}
|
||||
|
||||
private void ProcessString()
|
||||
{
|
||||
if (wantSemicolon || wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error. Require ':' or ','. Offset " + idx);
|
||||
|
||||
string value = FetchString();
|
||||
|
||||
if (wantKey)
|
||||
FillKey(value);
|
||||
else
|
||||
InsertNode(value, JsonElementType.String);
|
||||
}
|
||||
|
||||
private void GeneralProcess()
|
||||
{
|
||||
if (wantKey || wantSemicolon || wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error. Require '\"' or ':' or ','. Offset " + idx);
|
||||
|
||||
if (idx + 3 < json.Length)
|
||||
{
|
||||
string tmp = json.Substring(idx, 4);
|
||||
if (string.Equals(tmp, "true", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 4;
|
||||
InsertNode(true, JsonElementType.Boolean);
|
||||
return;
|
||||
}
|
||||
if (string.Equals(tmp, "null", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 4;
|
||||
InsertNode(null, JsonElementType.Null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx + 4 < json.Length)
|
||||
{
|
||||
string tmp = json.Substring(idx, 5);
|
||||
if (string.Equals(tmp, "false", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 5;
|
||||
InsertNode(false, JsonElementType.Boolean);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessNumber();
|
||||
}
|
||||
|
||||
private void ProcessNumber()
|
||||
{
|
||||
int dot = 0;
|
||||
int e = 0;
|
||||
int startIndex = idx;
|
||||
|
||||
while (idx < json.Length && numericalChars.Contains(json[idx]))
|
||||
{
|
||||
if (json[idx] == '.')
|
||||
dot++;
|
||||
else if (json[idx] == 'e' || json[idx] == 'E')
|
||||
e++;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (startIndex == idx)
|
||||
{
|
||||
if (idx + 7 < json.Length)
|
||||
{
|
||||
string tmp = json.Substring(idx, 8);
|
||||
if (string.Equals(tmp, "Infinity", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 8;
|
||||
InsertNode(Double.PositiveInfinity, JsonElementType.Double);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx + 2 < json.Length)
|
||||
{
|
||||
string tmp = json.Substring(idx, 3);
|
||||
if (string.Equals(tmp, "nan", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 3;
|
||||
InsertNode(Double.NaN, JsonElementType.Double);
|
||||
return;
|
||||
}
|
||||
if (string.Equals(tmp, "inf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 3;
|
||||
InsertNode(Double.PositiveInfinity, JsonElementType.Double);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidJsonException("Json Parser: content error. Unparseable content. Offset " + idx);
|
||||
}
|
||||
|
||||
if (e > 1 || dot > 1)
|
||||
throw new InvalidJsonException("Json Parser: content error. Unparseable content, invalid number. Offset " + idx);
|
||||
|
||||
string numberString = json.Substring(startIndex, idx - startIndex);
|
||||
|
||||
if (e > 0 || dot > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
double value = Convert.ToDouble(numberString);
|
||||
InsertNode(value, JsonElementType.Double);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: content error. Invalid number. Offset " + idx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
long value = Convert.ToInt64(numberString);
|
||||
InsertNode(value, JsonElementType.Int64);
|
||||
return;
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
UInt64 value = Convert.ToUInt64(numberString);
|
||||
InsertNode(value, JsonElementType.UInt64);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: content error. Invalid number. Offset " + idx, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67b78d9afdd27429c8705ad6b904740a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,322 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace com.fpnn.common
|
||||
{
|
||||
internal static class JsonStringEscaper
|
||||
{
|
||||
public delegate void CharEscape(StringBuilder sb, char c);
|
||||
|
||||
private static readonly Dictionary<char, CharEscape> _charEscapeDict;
|
||||
|
||||
static JsonStringEscaper()
|
||||
{
|
||||
_charEscapeDict = new Dictionary<char, CharEscape>
|
||||
{
|
||||
{ '\\', Slash },
|
||||
{ '"', QuotationMarks },
|
||||
{ '\b', SpecialChars },
|
||||
{ '\f', SpecialChars },
|
||||
{ '\n', SpecialChars },
|
||||
{ '\r', SpecialChars },
|
||||
{ '\t', SpecialChars },
|
||||
};
|
||||
}
|
||||
|
||||
static public void Escape(StringBuilder sb, string str)
|
||||
{
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
char c = str[i];
|
||||
if (_charEscapeDict.TryGetValue(c, out CharEscape escapeFunc))
|
||||
{
|
||||
escapeFunc(sb, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
ushort value = Convert.ToUInt16(c);
|
||||
|
||||
if (value > 0x1f && value < 0x7f) //-- ASCII visible chars
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("\\u");
|
||||
sb.Append(value.ToString("x4"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private void SpecialChars(StringBuilder sb, char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\b': sb.Append("\\b"); break;
|
||||
case '\f': sb.Append("\\f"); break;
|
||||
case '\n': sb.Append("\\n"); break;
|
||||
case '\r': sb.Append("\\r"); break;
|
||||
case '\t': sb.Append("\\t"); break;
|
||||
}
|
||||
}
|
||||
|
||||
static private void Slash(StringBuilder sb, char c)
|
||||
{
|
||||
sb.Append("\\\\");
|
||||
}
|
||||
|
||||
static private void QuotationMarks(StringBuilder sb, char c)
|
||||
{
|
||||
sb.Append("\\\"");
|
||||
}
|
||||
}
|
||||
|
||||
//============================[ Exception ]============================//
|
||||
|
||||
public class UnsupportedTypeException : JsonException
|
||||
{
|
||||
public UnsupportedTypeException(string message) : base(message) { }
|
||||
|
||||
static public UnsupportedTypeException Create(Object obj)
|
||||
{
|
||||
string typeFullName = obj.GetType().FullName;
|
||||
return new UnsupportedTypeException("FPJson unsupported type: " + typeFullName);
|
||||
}
|
||||
|
||||
static public UnsupportedTypeException DictionaryKey(Object obj)
|
||||
{
|
||||
string typeFullName = obj.GetType().FullName;
|
||||
return new UnsupportedTypeException("FPJson unsupported object key type: " + typeFullName);
|
||||
}
|
||||
}
|
||||
|
||||
//============================[ Json Stringify ]============================//
|
||||
|
||||
internal class JsonStringify
|
||||
{
|
||||
public delegate void Serialize(JsonStringify js, object obj);
|
||||
|
||||
private static readonly Dictionary<string, Serialize> _serializeDict;
|
||||
|
||||
static JsonStringify()
|
||||
{
|
||||
_serializeDict = new Dictionary<string, Serialize>
|
||||
{
|
||||
{ "System.Boolean", SerializeBoolean },
|
||||
|
||||
{ "System.Decimal", SerializeDecimal },
|
||||
{ "System.Double", SerializeDouble },
|
||||
{ "System.Single", SerializeFloat },
|
||||
|
||||
{ "System.SByte", SerializeInteger },
|
||||
{ "System.Int16", SerializeInteger },
|
||||
{ "System.Int32", SerializeInteger },
|
||||
{ "System.Int64", SerializeInteger },
|
||||
|
||||
{ "System.Byte", SerializeUInteger },
|
||||
{ "System.Char", SerializeUInteger },
|
||||
{ "System.UInt16", SerializeUInteger },
|
||||
{ "System.UInt32", SerializeUInteger },
|
||||
{ "System.UInt64", SerializeUInteger },
|
||||
|
||||
{ "System.String", SerializeString },
|
||||
{ "System.Tuple", SerializeTuple },
|
||||
{ "System.DateTime", SerializeTimestamp }
|
||||
};
|
||||
}
|
||||
|
||||
//-- Instance Methods
|
||||
|
||||
private StringBuilder sb;
|
||||
|
||||
public JsonStringify()
|
||||
{
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
public string Stringify(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
SerializeNull(this);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
string typeFullName = obj.GetType().FullName;
|
||||
int idx = typeFullName.IndexOf('`');
|
||||
if (idx != -1)
|
||||
{
|
||||
typeFullName = typeFullName.Substring(0, idx);
|
||||
}
|
||||
|
||||
if (_serializeDict.TryGetValue(typeFullName, out Serialize serializer))
|
||||
{
|
||||
serializer(this, obj);
|
||||
}
|
||||
else if (obj is IEnumerable)
|
||||
{
|
||||
SerializeIEnumerable(this, obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw UnsupportedTypeException.Create(obj);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
static private void SerializeBoolean(JsonStringify js, object obj)
|
||||
{
|
||||
Boolean v = (bool)obj;
|
||||
if (v == true)
|
||||
js.sb.Append("true");
|
||||
else
|
||||
js.sb.Append("false");
|
||||
}
|
||||
|
||||
static private void SerializeDecimal(JsonStringify js, object obj)
|
||||
{
|
||||
Decimal dec = (Decimal)obj;
|
||||
js.sb.Append(dec);
|
||||
}
|
||||
|
||||
static private void SerializeDouble(JsonStringify js, object obj)
|
||||
{
|
||||
double value = (double)obj;
|
||||
js.sb.Append(value);
|
||||
}
|
||||
|
||||
static private void SerializeFloat(JsonStringify js, object obj)
|
||||
{
|
||||
float value = (float)obj;
|
||||
js.sb.Append(value);
|
||||
}
|
||||
|
||||
static private void SerializeInteger(JsonStringify js, object obj)
|
||||
{
|
||||
Int64 value = (Int64)Convert.ChangeType(obj, TypeCode.Int64);
|
||||
js.sb.Append(value);
|
||||
}
|
||||
|
||||
static private void SerializeUInteger(JsonStringify js, object obj)
|
||||
{
|
||||
UInt64 value = (UInt64)Convert.ChangeType(obj, TypeCode.UInt64);
|
||||
js.sb.Append(value);
|
||||
}
|
||||
|
||||
static private void SerializeString(JsonStringify js, object obj)
|
||||
{
|
||||
string str = (string)obj;
|
||||
|
||||
js.sb.Append("\"");
|
||||
JsonStringEscaper.Escape(js.sb, str);
|
||||
js.sb.Append("\"");
|
||||
}
|
||||
|
||||
static private void SerializeTuple(JsonStringify js, object obj)
|
||||
{
|
||||
Type objType = obj.GetType();
|
||||
var props = objType.GetProperties();
|
||||
|
||||
bool isFirst = true;
|
||||
js.sb.Append("[");
|
||||
|
||||
foreach (System.Reflection.PropertyInfo prop in props)
|
||||
{
|
||||
if (isFirst)
|
||||
isFirst = false;
|
||||
else
|
||||
js.sb.Append(",");
|
||||
|
||||
object o = prop.GetValue(obj, null);
|
||||
js.Stringify(o);
|
||||
}
|
||||
|
||||
js.sb.Append("]");
|
||||
}
|
||||
static private void SerializeTimestamp(JsonStringify js, object obj)
|
||||
{
|
||||
DateTime userDate = (DateTime)obj;
|
||||
var univDateTime = userDate.ToUniversalTime();
|
||||
|
||||
js.sb.Append("\"");
|
||||
js.sb.Append(univDateTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
js.sb.Append("\"");
|
||||
}
|
||||
|
||||
static private void SerializeNull(JsonStringify js)
|
||||
{
|
||||
js.sb.Append("null");
|
||||
}
|
||||
|
||||
static private void SerializeArray(JsonStringify js, object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
bool isFirst = true;
|
||||
js.sb.Append("[");
|
||||
|
||||
while (it.MoveNext())
|
||||
{
|
||||
if (isFirst)
|
||||
isFirst = false;
|
||||
else
|
||||
js.sb.Append(",");
|
||||
|
||||
object o = it.Current;
|
||||
js.Stringify(o);
|
||||
}
|
||||
|
||||
js.sb.Append("]");
|
||||
}
|
||||
|
||||
static private void SerializeDictionary(JsonStringify js, object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
bool isFirst = true;
|
||||
js.sb.Append("{");
|
||||
|
||||
IDictionaryEnumerator id = (IDictionaryEnumerator)it;
|
||||
while (it.MoveNext())
|
||||
{
|
||||
if (isFirst)
|
||||
isFirst = false;
|
||||
else
|
||||
js.sb.Append(",");
|
||||
|
||||
object k = id.Key;
|
||||
if (Type.GetTypeCode(k.GetType()) != TypeCode.String)
|
||||
throw UnsupportedTypeException.DictionaryKey(k);
|
||||
|
||||
js.Stringify(k);
|
||||
|
||||
js.sb.Append(":");
|
||||
|
||||
object v = id.Value;
|
||||
js.Stringify(v);
|
||||
}
|
||||
|
||||
js.sb.Append("}");
|
||||
}
|
||||
|
||||
static private void SerializeIEnumerable(JsonStringify js, object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
if (it is IDictionaryEnumerator)
|
||||
SerializeDictionary(js, obj);
|
||||
else
|
||||
SerializeArray(js, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f777f23eee1ce44f8b5a9f635069c4d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,353 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace com.fpnn.common
|
||||
{
|
||||
/*
|
||||
* Close() is not necessary when using background thread.
|
||||
*
|
||||
* If using ErrorRecoder, please call TaskThreadPool.SetDefaultErrorRecorder()
|
||||
* before creating any instance, or call SetErrorRecorder() for each instance
|
||||
* before WakeUp()s are called.
|
||||
*/
|
||||
public class TaskThreadPool
|
||||
{
|
||||
public interface ITask
|
||||
{
|
||||
void Run();
|
||||
}
|
||||
|
||||
class ActionTask: ITask
|
||||
{
|
||||
Action action;
|
||||
|
||||
public ActionTask(Action act)
|
||||
{
|
||||
action = act;
|
||||
}
|
||||
public void Run()
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------[ fields ]-----------------------//
|
||||
private readonly int perfectCount;
|
||||
private readonly int maxCount;
|
||||
private readonly int maxQueueLength;
|
||||
|
||||
private static ErrorRecorder defaultErrorRecorder;
|
||||
|
||||
private class TaskThreadPoolCore
|
||||
{
|
||||
public bool backgroundThread;
|
||||
public int normalThreadCount;
|
||||
public int busyThreadCount;
|
||||
public int tempThreadCount;
|
||||
|
||||
public bool stopped;
|
||||
public Semaphore semaphore;
|
||||
public Semaphore quitSemaphore;
|
||||
public Queue<ITask> taskQueue;
|
||||
|
||||
public readonly int tempThreadLatencySeconds;
|
||||
public ErrorRecorder errorRecorder;
|
||||
|
||||
public TaskThreadPoolCore(int tempLatencySeconds, bool usingBackgroundThread)
|
||||
{
|
||||
backgroundThread = usingBackgroundThread;
|
||||
normalThreadCount = 0;
|
||||
busyThreadCount = 0;
|
||||
tempThreadCount = 0;
|
||||
|
||||
stopped = false;
|
||||
semaphore = new Semaphore(0, Int32.MaxValue);
|
||||
taskQueue = new Queue<ITask>();
|
||||
|
||||
tempThreadLatencySeconds = tempLatencySeconds;
|
||||
errorRecorder = defaultErrorRecorder;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly TaskThreadPoolCore core;
|
||||
|
||||
//----------------[ Constructor ]-----------------------//
|
||||
|
||||
public TaskThreadPool(int initThreadCount, int perfectThreadCount, int maxThreadCount, int maxQueueLengthLimitation = 0, int tempLatencySeconds = 60, bool usingBackGroundThread = true)
|
||||
{
|
||||
if (initThreadCount < 0)
|
||||
throw new ArgumentException("Param initThreadCount is less than Zero.", nameof(initThreadCount));
|
||||
|
||||
if (maxThreadCount <= 0)
|
||||
throw new ArgumentException("Param maxThreadCount is less than or equal to Zero.", nameof(maxThreadCount));
|
||||
|
||||
if (perfectThreadCount < initThreadCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(perfectThreadCount), "Param perfectThreadCount is less than initThreadCount");
|
||||
|
||||
if (maxThreadCount < perfectThreadCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxThreadCount), "Param maxThreadCount is less than perfectThreadCount");
|
||||
|
||||
perfectCount = perfectThreadCount;
|
||||
maxCount = maxThreadCount;
|
||||
maxQueueLength = maxQueueLengthLimitation;
|
||||
|
||||
core = new TaskThreadPoolCore(tempLatencySeconds, usingBackGroundThread);
|
||||
|
||||
for (int i = 0; i < initThreadCount; i++)
|
||||
{
|
||||
var thread = new Thread(Worker)
|
||||
{
|
||||
Name = "FPNN.ThreadPool.NormalWorker",
|
||||
IsBackground = core.backgroundThread
|
||||
};
|
||||
thread.Start(core);
|
||||
core.normalThreadCount++; //-- Unneed lock in there.
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetDefaultErrorRecorder(ErrorRecorder er)
|
||||
{
|
||||
defaultErrorRecorder = er;
|
||||
}
|
||||
|
||||
public void SetErrorRecorder(ErrorRecorder er)
|
||||
{
|
||||
core.errorRecorder = er;
|
||||
}
|
||||
|
||||
private void Append(bool normalThread)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread thread;
|
||||
if (normalThread)
|
||||
{
|
||||
thread = new Thread(Worker)
|
||||
{
|
||||
Name = "FPNN.ThreadPool.NormalWorker"
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
thread = new Thread(TempWorker)
|
||||
{
|
||||
Name = "FPNN.ThreadPool.TempWorker"
|
||||
};
|
||||
}
|
||||
|
||||
thread.IsBackground = core.backgroundThread;
|
||||
thread.Start(core);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
lock (core)
|
||||
{
|
||||
if (normalThread)
|
||||
core.normalThreadCount--;
|
||||
else
|
||||
core.tempThreadCount--;
|
||||
}
|
||||
core.errorRecorder?.RecordError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Wakeup(ITask task)
|
||||
{
|
||||
if (task == null)
|
||||
return false;
|
||||
|
||||
bool needAppend = false;
|
||||
bool appendNormalThread = false;
|
||||
lock (core)
|
||||
{
|
||||
if (core.stopped)
|
||||
return false;
|
||||
|
||||
if (maxQueueLength > 0 && core.taskQueue.Count >= maxQueueLength)
|
||||
return false;
|
||||
|
||||
core.taskQueue.Enqueue(task);
|
||||
|
||||
if (core.busyThreadCount + core.taskQueue.Count >= core.normalThreadCount + core.tempThreadCount)
|
||||
{
|
||||
if (core.normalThreadCount < perfectCount)
|
||||
{
|
||||
needAppend = true;
|
||||
appendNormalThread = true;
|
||||
core.normalThreadCount++;
|
||||
}
|
||||
else if (core.normalThreadCount + core.tempThreadCount < maxCount)
|
||||
{
|
||||
needAppend = true;
|
||||
appendNormalThread = false;
|
||||
core.tempThreadCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needAppend)
|
||||
{
|
||||
Append(appendNormalThread);
|
||||
}
|
||||
|
||||
core.semaphore.Release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Wakeup(Action action)
|
||||
{
|
||||
ActionTask task = new ActionTask(action);
|
||||
return Wakeup(task);
|
||||
}
|
||||
|
||||
//-- Please call this function in locked status.
|
||||
private static void ExitWorker(bool normal, TaskThreadPoolCore core)
|
||||
{
|
||||
if (normal)
|
||||
core.normalThreadCount--;
|
||||
else
|
||||
core.tempThreadCount--;
|
||||
|
||||
if (core.normalThreadCount == 0 && core.tempThreadCount == 0 && core.stopped && core.backgroundThread == false)
|
||||
core.quitSemaphore.Release();
|
||||
}
|
||||
|
||||
private static void Worker(Object obj)
|
||||
{
|
||||
TaskThreadPoolCore core = (TaskThreadPoolCore)obj;
|
||||
|
||||
while (true)
|
||||
{
|
||||
ITask task = null;
|
||||
core.semaphore.WaitOne();
|
||||
lock (core)
|
||||
{
|
||||
if (core.taskQueue.Count > 0)
|
||||
{
|
||||
task = core.taskQueue.Dequeue();
|
||||
core.busyThreadCount++;
|
||||
}
|
||||
else if (core.stopped)
|
||||
{
|
||||
ExitWorker(true, core);
|
||||
return;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
task.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (core.errorRecorder != null)
|
||||
core.errorRecorder.RecordError(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock(core)
|
||||
{
|
||||
core.busyThreadCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TempWorker(Object obj)
|
||||
{
|
||||
TaskThreadPoolCore core = (TaskThreadPoolCore)obj;
|
||||
int latencySeconds = core.tempThreadLatencySeconds;
|
||||
DateTime idleTime = DateTime.Now;
|
||||
|
||||
while (true)
|
||||
{
|
||||
ITask task = null;
|
||||
core.semaphore.WaitOne(latencySeconds * 1000);
|
||||
lock (core)
|
||||
{
|
||||
if (core.taskQueue.Count > 0)
|
||||
{
|
||||
task = core.taskQueue.Dequeue();
|
||||
core.busyThreadCount++;
|
||||
}
|
||||
else if (core.stopped)
|
||||
{
|
||||
ExitWorker(false, core);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
TimeSpan duration = DateTime.Now - idleTime;
|
||||
latencySeconds -= Convert.ToInt32(duration.TotalSeconds);
|
||||
|
||||
if (latencySeconds <= 0)
|
||||
{
|
||||
ExitWorker(false, core);
|
||||
return;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
task.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (core.errorRecorder != null)
|
||||
core.errorRecorder.RecordError(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (core)
|
||||
{
|
||||
core.busyThreadCount--;
|
||||
}
|
||||
}
|
||||
|
||||
idleTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
public void Close(bool dropAllTasks = false) //-- Synchronous method.
|
||||
{
|
||||
bool active = true;
|
||||
|
||||
lock (core)
|
||||
{
|
||||
if (core.stopped)
|
||||
return;
|
||||
|
||||
if (dropAllTasks)
|
||||
core.taskQueue.Clear();
|
||||
|
||||
if (core.backgroundThread || (core.normalThreadCount == 0 && core.tempThreadCount == 0))
|
||||
active = false;
|
||||
else
|
||||
core.quitSemaphore = new Semaphore(0, 1);
|
||||
|
||||
core.stopped = true;
|
||||
}
|
||||
|
||||
core.semaphore.Release(maxCount);
|
||||
|
||||
if (active)
|
||||
{
|
||||
core.quitSemaphore.WaitOne();
|
||||
core.quitSemaphore.Close();
|
||||
}
|
||||
|
||||
core.semaphore.Close();
|
||||
}
|
||||
|
||||
~TaskThreadPool()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66749982ee02845f4992ea52fe113376
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 623c0247982494d8ebd41db778b9532c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
namespace com.fpnn.msgpack
|
||||
{
|
||||
public class MsgPackException: Exception
|
||||
{
|
||||
public MsgPackException(string message) : base(message) { }
|
||||
public MsgPackException(String message, Exception ex) : base(message, ex) { }
|
||||
}
|
||||
|
||||
public class UnsupportedTypeException : MsgPackException
|
||||
{
|
||||
public UnsupportedTypeException(string message) : base(message) { }
|
||||
static public UnsupportedTypeException Create(Object obj)
|
||||
{
|
||||
string typeFullName = obj.GetType().FullName;
|
||||
return new UnsupportedTypeException("FPNN MsgPacker unsupported type: " + typeFullName);
|
||||
}
|
||||
}
|
||||
|
||||
public class UnrecognizedDataException : MsgPackException
|
||||
{
|
||||
public UnrecognizedDataException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
public class InsufficientException : MsgPackException
|
||||
{
|
||||
public InsufficientException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
public class InvalidDataException : MsgPackException
|
||||
{
|
||||
public InvalidDataException(string message) : base(message) { }
|
||||
public InvalidDataException(string message, Exception ex) : base(message, ex) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cbd313282dcd4053aa29bb130a65452
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,566 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace com.fpnn.msgpack
|
||||
{
|
||||
public static class MsgPacker
|
||||
{
|
||||
public delegate void PackItem(Stream stream, Object obj);
|
||||
|
||||
private static readonly Dictionary<string, PackItem> _packDict;
|
||||
|
||||
static MsgPacker()
|
||||
{
|
||||
_packDict = new Dictionary<string, PackItem>
|
||||
{
|
||||
{ "System.Boolean", PackBoolean },
|
||||
|
||||
{ "System.Decimal", PackDecimal },
|
||||
{ "System.Double", PackDouble },
|
||||
{ "System.Single", PackFloat },
|
||||
|
||||
{ "System.SByte", PackInteger },
|
||||
{ "System.Int16", PackInteger },
|
||||
{ "System.Int32", PackInteger },
|
||||
{ "System.Int64", PackInteger },
|
||||
|
||||
{ "System.Byte", PackUInteger },
|
||||
{ "System.Char", PackUInteger },
|
||||
{ "System.UInt16", PackUInteger },
|
||||
{ "System.UInt32", PackUInteger },
|
||||
{ "System.UInt64", PackUInteger },
|
||||
|
||||
{ "System.String", PackString },
|
||||
{ "System.Tuple", PackTuple },
|
||||
{ "System.DateTime", PackTimestamp },
|
||||
{ "System.Byte[]", PackBinary }
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* May throw exception in UnsupportedTypeException type.
|
||||
*/
|
||||
static public void Pack(Stream stream, Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
PackNull(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
string typeFullName = obj.GetType().FullName;
|
||||
int idx = typeFullName.IndexOf('`');
|
||||
if (idx != -1)
|
||||
{
|
||||
typeFullName = typeFullName.Substring(0, idx);
|
||||
}
|
||||
|
||||
if (_packDict.TryGetValue(typeFullName, out PackItem packer))
|
||||
{
|
||||
packer(stream, obj);
|
||||
}
|
||||
else if (obj is IEnumerable)
|
||||
{
|
||||
PackIEnumerable(stream, obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw UnsupportedTypeException.Create(obj);
|
||||
}
|
||||
}
|
||||
|
||||
static public void PackNull(Stream stream)
|
||||
{
|
||||
byte sign = 0xc0;
|
||||
stream.WriteByte(sign);
|
||||
}
|
||||
|
||||
static public void PackBoolean(Stream stream, Object obj)
|
||||
{
|
||||
Boolean v = (bool)obj;
|
||||
if (v)
|
||||
{
|
||||
byte sign = 0xc3;
|
||||
stream.WriteByte(sign);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte sign = 0xc2;
|
||||
stream.WriteByte(sign);
|
||||
}
|
||||
}
|
||||
|
||||
static public void PackInteger(Stream stream, Object obj)
|
||||
{
|
||||
Int64 value = (Int64)Convert.ChangeType(obj, TypeCode.Int64);
|
||||
|
||||
if (value >= 0)
|
||||
{
|
||||
PackUInteger(stream, obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((0xFFFFFFFFFFFFFFE0 & (UInt64)value) == 0xFFFFFFFFFFFFFFE0)
|
||||
{
|
||||
sbyte sbyteValue = (sbyte)value;
|
||||
stream.WriteByte((byte)sbyteValue);
|
||||
}
|
||||
else if ((0xFFFFFFFFFFFFFF80 & (UInt64)value) == 0xFFFFFFFFFFFFFF80)
|
||||
{
|
||||
sbyte sbyteValue = (sbyte)value;
|
||||
|
||||
byte sign = 0xd0;
|
||||
stream.WriteByte(sign);
|
||||
stream.WriteByte((byte)sbyteValue);
|
||||
}
|
||||
else if ((0xFFFFFFFFFFFF8000 & (UInt64)value) == 0xFFFFFFFFFFFF8000)
|
||||
{
|
||||
short shortValue = (short)value;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(shortValue);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xd1;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 2);
|
||||
}
|
||||
else if ((0xFFFFFFFF80000000 & (UInt64)value) == 0xFFFFFFFF80000000)
|
||||
{
|
||||
Int32 int32Value = (Int32)value;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(int32Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xd2;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
Int64 int64Value = (Int64)value;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(int64Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xd3;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
static public void PackUInteger(Stream stream, Object obj)
|
||||
{
|
||||
UInt64 value = (UInt64)Convert.ChangeType(obj, TypeCode.UInt64);
|
||||
|
||||
if (value <= 127)
|
||||
{
|
||||
byte byteValue = (byte)value;
|
||||
stream.WriteByte(byteValue);
|
||||
}
|
||||
else if (value <= 0xFF)
|
||||
{
|
||||
byte sign = 0xcc;
|
||||
stream.WriteByte(sign);
|
||||
|
||||
byte byteValue = (byte)value;
|
||||
stream.WriteByte(byteValue);
|
||||
}
|
||||
else if (value <= 0xFFFF)
|
||||
{
|
||||
ushort shortValue = (ushort)value;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(shortValue);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xcd;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 2);
|
||||
}
|
||||
else if (value <= 0xFFFFFFFF)
|
||||
{
|
||||
UInt32 uint32Value = (UInt32)value;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(uint32Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xce;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt64 uint64Value = (UInt64)value;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(uint64Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xcf;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
static public void PackFloat(Stream stream, Object obj)
|
||||
{
|
||||
//-- float in C# memory already is IEEE 754
|
||||
|
||||
float value = (float)obj;
|
||||
byte[] byteArray = BitConverter.GetBytes(value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(byteArray);
|
||||
|
||||
byte sign = 0xca;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(byteArray, 0, 4);
|
||||
}
|
||||
|
||||
static public void PackDouble(Stream stream, Object obj)
|
||||
{
|
||||
//-- double in C# memory already is IEEE 754
|
||||
|
||||
double value = (double)obj;
|
||||
byte[] byteArray = BitConverter.GetBytes(value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(byteArray);
|
||||
|
||||
byte sign = 0xcb;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(byteArray, 0, 8);
|
||||
}
|
||||
|
||||
static public void PackDecimal(Stream stream, Object obj)
|
||||
{
|
||||
Decimal dec = (Decimal)obj;
|
||||
double value = Decimal.ToDouble(dec);
|
||||
|
||||
PackDouble(stream, value);
|
||||
}
|
||||
|
||||
static public void PackString(Stream stream, Object obj)
|
||||
{
|
||||
string stringValue = (string)obj;
|
||||
|
||||
UTF8Encoding utf8Encoding = new UTF8Encoding(); //-- NO BOM.
|
||||
byte[] rawData = utf8Encoding.GetBytes(stringValue);
|
||||
|
||||
if (rawData.Length <= 31)
|
||||
{
|
||||
byte sign = (byte)(rawData.Length | 0xA0);
|
||||
stream.WriteByte(sign);
|
||||
}
|
||||
else if (rawData.Length <= 255)
|
||||
{
|
||||
byte sign = 0xd9;
|
||||
stream.WriteByte(sign);
|
||||
|
||||
byte lengthByte = (byte)rawData.Length;
|
||||
stream.WriteByte(lengthByte);
|
||||
}
|
||||
else if (rawData.Length <= 65535)
|
||||
{
|
||||
byte sign = 0xda;
|
||||
stream.WriteByte(sign);
|
||||
|
||||
ushort shortValue = (ushort)rawData.Length;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(shortValue);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
stream.Write(lenBuffer, 0, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte sign = 0xdb;
|
||||
stream.WriteByte(sign);
|
||||
|
||||
UInt32 uint32Value = (UInt32)rawData.Length;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(uint32Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
stream.Write(lenBuffer, 0, 4);
|
||||
}
|
||||
|
||||
stream.Write(rawData, 0, rawData.Length);
|
||||
}
|
||||
|
||||
static public void PackBinary(Stream stream, Object obj)
|
||||
{
|
||||
byte[] binaryData = (byte[])obj;
|
||||
|
||||
if (binaryData.Length <= 255)
|
||||
{
|
||||
byte sign = 0xc4;
|
||||
stream.WriteByte(sign);
|
||||
|
||||
byte lengthByte = (byte)binaryData.Length;
|
||||
stream.WriteByte(lengthByte);
|
||||
}
|
||||
else if (binaryData.Length <= 65535)
|
||||
{
|
||||
byte sign = 0xc5;
|
||||
stream.WriteByte(sign);
|
||||
|
||||
ushort shortValue = (ushort)binaryData.Length;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(shortValue);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
stream.Write(lenBuffer, 0, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte sign = 0xc6;
|
||||
stream.WriteByte(sign);
|
||||
|
||||
UInt32 uint32Value = (UInt32)binaryData.Length;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(uint32Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
stream.Write(lenBuffer, 0, 4);
|
||||
}
|
||||
|
||||
stream.Write(binaryData, 0, binaryData.Length);
|
||||
}
|
||||
|
||||
static public void PackArray(Stream stream, Object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
int count = 0;
|
||||
|
||||
while (it.MoveNext())
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count <= 15)
|
||||
{
|
||||
byte sign = (byte)(count | 0x90);
|
||||
stream.WriteByte(sign);
|
||||
}
|
||||
else if (count <= 65535)
|
||||
{
|
||||
ushort shortValue = (ushort)count;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(shortValue);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xdc;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt32 uint32Value = (UInt32)count;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(uint32Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xdd;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 4);
|
||||
}
|
||||
|
||||
it.Reset();
|
||||
|
||||
while (it.MoveNext())
|
||||
{
|
||||
Object o = it.Current;
|
||||
Pack(stream, o);
|
||||
}
|
||||
}
|
||||
|
||||
static public void PackDictionary(Stream stream, Object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
int count = 0;
|
||||
|
||||
while (it.MoveNext())
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count <= 15)
|
||||
{
|
||||
byte sign = (byte)(count | 0x80);
|
||||
stream.WriteByte(sign);
|
||||
}
|
||||
else if (count <= 65535)
|
||||
{
|
||||
ushort shortValue = (ushort)count;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(shortValue);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xde;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt32 uint32Value = (UInt32)count;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(uint32Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xdf;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 4);
|
||||
}
|
||||
|
||||
it.Reset();
|
||||
IDictionaryEnumerator id = (IDictionaryEnumerator)it;
|
||||
while (it.MoveNext())
|
||||
{
|
||||
Object k = id.Key;
|
||||
Pack(stream, k);
|
||||
|
||||
Object v = id.Value;
|
||||
Pack(stream, v);
|
||||
}
|
||||
}
|
||||
|
||||
static public void PackTimestamp(Stream stream, Object obj)
|
||||
{
|
||||
DateTime userDate = (DateTime)obj;
|
||||
DateTime utcUserDate = userDate.ToUniversalTime();
|
||||
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
TimeSpan diff = utcUserDate - origin;
|
||||
Int64 seconds = (Int64)Math.Floor(diff.TotalSeconds);
|
||||
Int64 milliseconds = (Int64)Math.Floor(diff.TotalMilliseconds);
|
||||
Int64 nonaseconds = (milliseconds - seconds * 1000) * 1000 * 1000;
|
||||
|
||||
if (nonaseconds == 0 && ((UInt64)seconds >> 32) == 0)
|
||||
{
|
||||
UInt32 secondsValue = (UInt32)seconds;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(secondsValue);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xd6;
|
||||
stream.WriteByte(sign);
|
||||
sbyte type = -1;
|
||||
stream.WriteByte((byte)type);
|
||||
stream.Write(lenBuffer, 0, 4);
|
||||
}
|
||||
else if (((UInt64)seconds >> 34) == 0)
|
||||
{
|
||||
UInt64 rawData = ((UInt64)nonaseconds << 34) | (UInt64)seconds;
|
||||
byte[] byteArray = BitConverter.GetBytes(rawData);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(byteArray);
|
||||
|
||||
byte sign = 0xd7;
|
||||
stream.WriteByte(sign);
|
||||
sbyte type = -1;
|
||||
stream.WriteByte((byte)type);
|
||||
stream.Write(byteArray, 0, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt32 nanos = (UInt32)nonaseconds;
|
||||
byte[] secondsBytes = BitConverter.GetBytes(seconds);
|
||||
byte[] nanosBytes = BitConverter.GetBytes(nanos);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(secondsBytes);
|
||||
Array.Reverse(nanosBytes);
|
||||
}
|
||||
|
||||
byte sign = 0xc7;
|
||||
stream.WriteByte(sign);
|
||||
sign = 12;
|
||||
stream.WriteByte(sign);
|
||||
sbyte type = -1;
|
||||
stream.WriteByte((byte)type);
|
||||
stream.Write(nanosBytes, 0, 4);
|
||||
stream.Write(secondsBytes, 0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
static public void PackTuple(Stream stream, Object obj)
|
||||
{
|
||||
Type objType = obj.GetType();
|
||||
var props = objType.GetProperties();
|
||||
int count = props.Length;
|
||||
|
||||
if (count <= 15)
|
||||
{
|
||||
byte sign = (byte)(count | 0x90);
|
||||
stream.WriteByte(sign);
|
||||
}
|
||||
else if (count <= 65535)
|
||||
{
|
||||
ushort shortValue = (ushort)count;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(shortValue);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xdc;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt32 uint32Value = (UInt32)count;
|
||||
byte[] lenBuffer = BitConverter.GetBytes(uint32Value);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(lenBuffer);
|
||||
|
||||
byte sign = 0xdd;
|
||||
stream.WriteByte(sign);
|
||||
stream.Write(lenBuffer, 0, 4);
|
||||
}
|
||||
|
||||
foreach (System.Reflection.PropertyInfo prop in props)
|
||||
{
|
||||
Object subObj = prop.GetValue(obj, null);
|
||||
Pack(stream, obj);
|
||||
}
|
||||
}
|
||||
|
||||
static public void PackIEnumerable(Stream stream, Object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
if (it is IDictionaryEnumerator)
|
||||
PackDictionary(stream, obj);
|
||||
else
|
||||
PackArray(stream, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90c2b19d237af4d54ab342ee0ce9ecb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,868 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace com.fpnn.msgpack
|
||||
{
|
||||
public static class MsgUnpacker
|
||||
{
|
||||
public static Dictionary<Object, Object> Unpack(byte[] binary)
|
||||
{
|
||||
return Unpack(binary, 0);
|
||||
}
|
||||
|
||||
public static Dictionary<Object, Object> Unpack(byte[] binary, int offset, int length = 0)
|
||||
{
|
||||
SAXInfo info = new SAXInfo(binary)
|
||||
{
|
||||
currIdx = offset
|
||||
};
|
||||
|
||||
int endIndx = info.binary.Length;
|
||||
if (length > 0)
|
||||
endIndx = Math.Min(offset + length, info.binary.Length);
|
||||
|
||||
try
|
||||
{
|
||||
while (endIndx > info.currIdx)
|
||||
{
|
||||
UnpackNext(info);
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException e)
|
||||
{
|
||||
throw new InvalidDataException("Incomplete msgPack binary data.", e);
|
||||
}
|
||||
|
||||
if (info.GetRootObject() is Dictionary<Object, Object> dict)
|
||||
return dict;
|
||||
|
||||
throw new InvalidDataException("MsgPack binary is not dictionary.");
|
||||
}
|
||||
|
||||
public static Object Unpack(byte[] binary, int offset, out int endOffset)
|
||||
{
|
||||
if (binary == null || binary.Length == 0)
|
||||
throw new InvalidDataException("MsgPack binary is empty.");
|
||||
|
||||
SAXInfo info = new SAXInfo(binary)
|
||||
{
|
||||
currIdx = offset
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
UnpackNext(info);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException e)
|
||||
{
|
||||
throw new InvalidDataException("Incomplete msgPack binary data.", e);
|
||||
}
|
||||
|
||||
endOffset = info.currIdx;
|
||||
|
||||
return info.GetRootObject();
|
||||
}
|
||||
|
||||
//------------[ Private Methods ]-----------//
|
||||
|
||||
private class ContainerInfo
|
||||
{
|
||||
private Dictionary<object, object> dict;
|
||||
private List<object> list;
|
||||
private object key;
|
||||
private bool wantKey;
|
||||
|
||||
public ContainerInfo(bool isDictionary, int capacity)
|
||||
{
|
||||
if (isDictionary)
|
||||
{
|
||||
dict = new Dictionary<object, object>(capacity);
|
||||
wantKey = true;
|
||||
}
|
||||
else
|
||||
list = new List<object>(capacity);
|
||||
}
|
||||
|
||||
public Object GetContainer()
|
||||
{
|
||||
if (dict != null)
|
||||
{
|
||||
if (wantKey == false)
|
||||
throw new InsufficientException("Invalid msgPack binary: dictionary lost value.");
|
||||
return dict;
|
||||
}
|
||||
else
|
||||
return list;
|
||||
}
|
||||
|
||||
public void Add(Object obj)
|
||||
{
|
||||
if (dict != null)
|
||||
{
|
||||
if (wantKey)
|
||||
{
|
||||
key = obj;
|
||||
wantKey = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
dict.Add(key, obj);
|
||||
wantKey = true;
|
||||
key = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SAXInfo
|
||||
{
|
||||
private Stack<ContainerInfo> containerStack;
|
||||
private object rootbject;
|
||||
//-- public fields
|
||||
public readonly byte[] binary;
|
||||
public int currIdx;
|
||||
|
||||
public SAXInfo(byte[] binary)
|
||||
{
|
||||
containerStack = new Stack<ContainerInfo>();
|
||||
this.binary = binary;
|
||||
currIdx = 0;
|
||||
}
|
||||
|
||||
public void Add(object obj)
|
||||
{
|
||||
if (containerStack.Count > 0)
|
||||
{
|
||||
containerStack.Peek().Add(obj);
|
||||
}
|
||||
else if (rootbject == null)
|
||||
{
|
||||
rootbject = obj;
|
||||
}
|
||||
else
|
||||
throw new InvalidDataException("MsgPack binary has parallel roots.");
|
||||
}
|
||||
|
||||
public void AddContainer(ContainerInfo container)
|
||||
{
|
||||
Add(container.GetContainer());
|
||||
containerStack.Push(container);
|
||||
}
|
||||
|
||||
public void PopContainer()
|
||||
{
|
||||
containerStack.Pop();
|
||||
}
|
||||
|
||||
public Object GetRootObject()
|
||||
{
|
||||
if (containerStack.Count > 0)
|
||||
throw new InsufficientException("Invalid msgPack binary: insufficient binary data.");
|
||||
|
||||
return rootbject;
|
||||
}
|
||||
}
|
||||
|
||||
private delegate void UnpackItem(SAXInfo info);
|
||||
|
||||
private static readonly Dictionary<byte, UnpackItem> _unpackDict;
|
||||
|
||||
static MsgUnpacker()
|
||||
{
|
||||
_unpackDict = new Dictionary<byte, UnpackItem>
|
||||
{
|
||||
{ 0xc0, UnpackNil },
|
||||
// { 0xc1, UnpackData }, //-- (never used) in msgpack SPEC: https://github.com/msgpack/msgpack/blob/master/spec.md
|
||||
{ 0xc2, UnpackFalse },
|
||||
{ 0xc3, UnpackTrue },
|
||||
|
||||
{ 0xc4, UnpackBin8 },
|
||||
{ 0xc5, UnpackBin16 },
|
||||
{ 0xc6, UnpackBin32 },
|
||||
{ 0xc7, UnpackExt8 },
|
||||
|
||||
{ 0xc8, UnpackExt16 },
|
||||
{ 0xc9, UnpackExt32 },
|
||||
{ 0xca, UnpackFloat32 },
|
||||
{ 0xcb, UnpackFloat64 },
|
||||
|
||||
{ 0xcc, UnpackUInt8 },
|
||||
{ 0xcd, UnpackUInt16 },
|
||||
{ 0xce, UnpackUInt32 },
|
||||
{ 0xcf, UnpackUInt64 },
|
||||
|
||||
{ 0xd0, UnpackInt8 },
|
||||
{ 0xd1, UnpackInt16 },
|
||||
{ 0xd2, UnpackInt32 },
|
||||
{ 0xd3, UnpackInt64 },
|
||||
|
||||
{ 0xd4, UnpackFixExt1 },
|
||||
{ 0xd5, UnpackFixExt2 },
|
||||
{ 0xd6, UnpackFixExt4 },
|
||||
{ 0xd7, UnpackFixExt8 },
|
||||
|
||||
{ 0xd8, UnpackFixExt16 },
|
||||
{ 0xd9, UnpackStr8 },
|
||||
{ 0xda, UnpackStr16 },
|
||||
{ 0xdb, UnpackStr32 },
|
||||
|
||||
{ 0xdc, UnpackArray16 },
|
||||
{ 0xdd, UnpackArray32 },
|
||||
{ 0xde, UnpackMap16 },
|
||||
{ 0xdf, UnpackMap32 },
|
||||
};
|
||||
}
|
||||
|
||||
private static void UnpackNext(SAXInfo info)
|
||||
{
|
||||
byte sign = info.binary[info.currIdx];
|
||||
info.currIdx++;
|
||||
|
||||
if (_unpackDict.TryGetValue(sign, out UnpackItem unpacker))
|
||||
{
|
||||
unpacker(info);
|
||||
}
|
||||
else if (sign <= 0x7f)
|
||||
{
|
||||
UnpackPositiveFixInt(info);
|
||||
}
|
||||
else if (sign <= 0x8f)
|
||||
{
|
||||
UnpackFixMap(info);
|
||||
}
|
||||
else if (sign <= 0x9f)
|
||||
{
|
||||
UnpackFixArray(info);
|
||||
}
|
||||
else if (sign <= 0xbf)
|
||||
{
|
||||
UnpackFixStr(info);
|
||||
}
|
||||
else if (sign <= 0xff)
|
||||
{
|
||||
UnpackNegativeFixInt(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnrecognizedDataException("Msgpack first byte: 0x" + sign.ToString("X2"));
|
||||
}
|
||||
}
|
||||
|
||||
//------------[ Delegate Methods ]-----------//
|
||||
|
||||
private static void UnpackNil(SAXInfo info)
|
||||
{
|
||||
info.Add(null);
|
||||
}
|
||||
|
||||
private static void UnpackFalse(SAXInfo info)
|
||||
{
|
||||
info.Add(false);
|
||||
}
|
||||
|
||||
private static void UnpackTrue(SAXInfo info)
|
||||
{
|
||||
info.Add(true);
|
||||
}
|
||||
|
||||
private static void UnpackBin8(SAXInfo info)
|
||||
{
|
||||
byte length = info.binary[info.currIdx];
|
||||
info.currIdx++;
|
||||
|
||||
byte[] data = new byte[length];
|
||||
Array.Copy(info.binary, info.currIdx, data, 0, length);
|
||||
info.currIdx += length;
|
||||
|
||||
info.Add(data);
|
||||
}
|
||||
|
||||
private static void UnpackBin16(SAXInfo info)
|
||||
{
|
||||
ushort length;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[2];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 2);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
length = BitConverter.ToUInt16(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = BitConverter.ToUInt16(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 2;
|
||||
|
||||
|
||||
byte[] data = new byte[length];
|
||||
Array.Copy(info.binary, info.currIdx, data, 0, length);
|
||||
info.currIdx += length;
|
||||
|
||||
info.Add(data);
|
||||
}
|
||||
|
||||
private static void UnpackBin32(SAXInfo info)
|
||||
{
|
||||
UInt32 length;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
length = BitConverter.ToUInt32(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = BitConverter.ToUInt32(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 4;
|
||||
|
||||
|
||||
byte[] data = new byte[length];
|
||||
Array.Copy(info.binary, info.currIdx, data, 0, length);
|
||||
info.currIdx += (int)length;
|
||||
|
||||
info.Add(data);
|
||||
}
|
||||
|
||||
private static void UnpackExt8(SAXInfo info)
|
||||
{
|
||||
byte length = info.binary[info.currIdx];
|
||||
sbyte type = (sbyte)info.binary[info.currIdx + 1];
|
||||
|
||||
if (length != 12 || type != -1)
|
||||
throw new UnsupportedTypeException("Unsupported msgPack ext8 format. type " + type + ", data length: " + length);
|
||||
|
||||
info.currIdx += 2;
|
||||
|
||||
byte[] secondsBuffer = new byte[8];
|
||||
byte[] nanosecondsBuffer = new byte[4];
|
||||
|
||||
Array.Copy(info.binary, info.currIdx, nanosecondsBuffer, 0, 4);
|
||||
Array.Copy(info.binary, info.currIdx + 4, secondsBuffer, 0, 8);
|
||||
info.currIdx += 12;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(secondsBuffer);
|
||||
Array.Reverse(nanosecondsBuffer);
|
||||
}
|
||||
|
||||
UInt32 nanoseconds = BitConverter.ToUInt32(nanosecondsBuffer, 0);
|
||||
Int64 seconds = BitConverter.ToInt64(secondsBuffer, 0);
|
||||
|
||||
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
TimeSpan offset = new TimeSpan(seconds * 1000 * 1000 * 10 + nanoseconds / 100);
|
||||
|
||||
info.Add(origin.Add(offset));
|
||||
}
|
||||
|
||||
private static void UnpackExt16(SAXInfo info)
|
||||
{
|
||||
sbyte type = (sbyte)info.binary[info.currIdx + 2];
|
||||
throw new UnsupportedTypeException("Unsupported msgPack ext16 format. type " + type);
|
||||
}
|
||||
|
||||
private static void UnpackExt32(SAXInfo info)
|
||||
{
|
||||
sbyte type = (sbyte)info.binary[info.currIdx + 4];
|
||||
throw new UnsupportedTypeException("Unsupported msgPack ext32 format. type " + type);
|
||||
}
|
||||
|
||||
private static void UnpackFloat32(SAXInfo info)
|
||||
{
|
||||
//-- float in C# memory already is IEEE 754
|
||||
|
||||
float value;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
value = BitConverter.ToSingle(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BitConverter.ToSingle(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 4;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackFloat64(SAXInfo info)
|
||||
{
|
||||
//-- float in C# memory already is IEEE 754
|
||||
|
||||
double value;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[8];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 8);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
value = BitConverter.ToDouble(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BitConverter.ToDouble(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 8;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackUInt8(SAXInfo info)
|
||||
{
|
||||
byte value = info.binary[info.currIdx];
|
||||
info.currIdx++;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackUInt16(SAXInfo info)
|
||||
{
|
||||
ushort value;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[2];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 2);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
value = BitConverter.ToUInt16(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BitConverter.ToUInt16(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 2;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackUInt32(SAXInfo info)
|
||||
{
|
||||
UInt32 value;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
value = BitConverter.ToUInt32(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BitConverter.ToUInt32(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 4;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackUInt64(SAXInfo info)
|
||||
{
|
||||
UInt64 value;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[8];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 8);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
value = BitConverter.ToUInt64(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BitConverter.ToUInt64(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 8;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackInt8(SAXInfo info)
|
||||
{
|
||||
sbyte value = (sbyte)info.binary[info.currIdx];
|
||||
info.currIdx++;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackInt16(SAXInfo info)
|
||||
{
|
||||
short value;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[2];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 2);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
value = BitConverter.ToInt16(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BitConverter.ToInt16(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 2;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackInt32(SAXInfo info)
|
||||
{
|
||||
Int32 value;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
value = BitConverter.ToInt32(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BitConverter.ToInt32(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 4;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackInt64(SAXInfo info)
|
||||
{
|
||||
Int64 value;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[8];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 8);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
value = BitConverter.ToInt64(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BitConverter.ToInt64(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 8;
|
||||
|
||||
info.Add(value);
|
||||
}
|
||||
|
||||
private static void UnpackFixExt1(SAXInfo info)
|
||||
{
|
||||
sbyte type = (sbyte)info.binary[info.currIdx];
|
||||
throw new UnsupportedTypeException("Unsupported msgPack fix ext 1 format. type " + type);
|
||||
}
|
||||
private static void UnpackFixExt2(SAXInfo info)
|
||||
{
|
||||
sbyte type = (sbyte)info.binary[info.currIdx];
|
||||
throw new UnsupportedTypeException("Unsupported msgPack fix ext 2 format. type " + type);
|
||||
}
|
||||
private static void UnpackFixExt4(SAXInfo info)
|
||||
{
|
||||
sbyte type = (sbyte)info.binary[info.currIdx];
|
||||
if (type != -1)
|
||||
throw new UnsupportedTypeException("Unsupported msgPack fix ext 4 format. type " + type);
|
||||
|
||||
info.currIdx++;
|
||||
|
||||
|
||||
UInt32 seconds;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
seconds = BitConverter.ToUInt32(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
seconds = BitConverter.ToUInt32(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 4;
|
||||
|
||||
|
||||
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
TimeSpan offset = new TimeSpan(seconds * 1000 * 1000 * 10);
|
||||
|
||||
info.Add(origin.Add(offset));
|
||||
}
|
||||
private static void UnpackFixExt8(SAXInfo info)
|
||||
{
|
||||
sbyte type = (sbyte)info.binary[info.currIdx];
|
||||
if (type != -1)
|
||||
throw new UnsupportedTypeException("Unsupported msgPack fix ext 8 format. type " + type);
|
||||
|
||||
info.currIdx++;
|
||||
|
||||
|
||||
UInt64 timeValue;
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[8];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 8);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
timeValue = BitConverter.ToUInt64(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
timeValue = BitConverter.ToUInt64(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 8;
|
||||
|
||||
|
||||
UInt32 nanoseconds = (UInt32)(timeValue >> 34);
|
||||
Int64 seconds = (Int64)(timeValue & 0x00000003FFFFFFFF);
|
||||
|
||||
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
TimeSpan offset = new TimeSpan(seconds * 1000 * 1000 * 10 + nanoseconds / 100);
|
||||
|
||||
info.Add(origin.Add(offset));
|
||||
}
|
||||
private static void UnpackFixExt16(SAXInfo info)
|
||||
{
|
||||
sbyte type = (sbyte)info.binary[info.currIdx];
|
||||
throw new UnsupportedTypeException("Unsupported msgPack fix ext 16 format. type " + type);
|
||||
}
|
||||
|
||||
private static void UnpackString(SAXInfo info, int length)
|
||||
{
|
||||
UTF8Encoding utf8Encoding = new UTF8Encoding(false, true); //-- NO BOM.
|
||||
try
|
||||
{
|
||||
string str = utf8Encoding.GetString(info.binary, info.currIdx, length);
|
||||
info.currIdx += length;
|
||||
info.Add(str);
|
||||
return;
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (DecoderFallbackException)
|
||||
{
|
||||
//-- Do nothing, through the cache block.
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
//-- Do nothing, through the cache block.
|
||||
}
|
||||
|
||||
byte[] data = new byte[length];
|
||||
Array.Copy(info.binary, info.currIdx, data, 0, length);
|
||||
info.currIdx += length;
|
||||
info.Add(data);
|
||||
}
|
||||
private static void UnpackStr8(SAXInfo info)
|
||||
{
|
||||
byte length = info.binary[info.currIdx];
|
||||
info.currIdx++;
|
||||
|
||||
UnpackString(info, length);
|
||||
}
|
||||
private static void UnpackStr16(SAXInfo info)
|
||||
{
|
||||
ushort length;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[2];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 2);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
length = BitConverter.ToUInt16(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = BitConverter.ToUInt16(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 2;
|
||||
|
||||
UnpackString(info, length);
|
||||
}
|
||||
private static void UnpackStr32(SAXInfo info)
|
||||
{
|
||||
UInt32 length;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
length = BitConverter.ToUInt32(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = BitConverter.ToUInt32(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 4;
|
||||
|
||||
UnpackString(info, (int)length);
|
||||
}
|
||||
private static void UnpackArray16(SAXInfo info)
|
||||
{
|
||||
ushort length;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[2];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 2);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
length = BitConverter.ToUInt16(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = BitConverter.ToUInt16(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 2;
|
||||
|
||||
ContainerInfo container = new ContainerInfo(false, (int)length);
|
||||
info.AddContainer(container);
|
||||
|
||||
for (ushort i = 0; i < length; i++)
|
||||
UnpackNext(info);
|
||||
|
||||
info.PopContainer();
|
||||
}
|
||||
private static void UnpackArray32(SAXInfo info)
|
||||
{
|
||||
UInt32 length;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
length = BitConverter.ToUInt32(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = BitConverter.ToUInt32(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 4;
|
||||
|
||||
ContainerInfo container = new ContainerInfo(false, (int)length);
|
||||
info.AddContainer(container);
|
||||
|
||||
for (UInt32 i = 0; i < length; i++)
|
||||
UnpackNext(info);
|
||||
|
||||
info.PopContainer();
|
||||
}
|
||||
|
||||
private static void UnpackMap16(SAXInfo info)
|
||||
{
|
||||
ushort length;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[2];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 2);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
length = BitConverter.ToUInt16(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = BitConverter.ToUInt16(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 2;
|
||||
|
||||
ContainerInfo container = new ContainerInfo(true, (int)length);
|
||||
info.AddContainer(container);
|
||||
|
||||
for (UInt32 i = 0; i < length * 2; i++)
|
||||
UnpackNext(info);
|
||||
|
||||
info.PopContainer();
|
||||
}
|
||||
private static void UnpackMap32(SAXInfo info)
|
||||
{
|
||||
UInt32 length;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] lengthBuffer = new byte[4];
|
||||
Array.Copy(info.binary, info.currIdx, lengthBuffer, 0, 4);
|
||||
Array.Reverse(lengthBuffer);
|
||||
|
||||
length = BitConverter.ToUInt32(lengthBuffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = BitConverter.ToUInt32(info.binary, info.currIdx);
|
||||
}
|
||||
info.currIdx += 4;
|
||||
|
||||
ContainerInfo container = new ContainerInfo(true, (int)length);
|
||||
info.AddContainer(container);
|
||||
|
||||
for (UInt32 i = 0; i < length * 2; i++)
|
||||
UnpackNext(info);
|
||||
|
||||
info.PopContainer();
|
||||
}
|
||||
private static void UnpackPositiveFixInt(SAXInfo info)
|
||||
{
|
||||
sbyte value = (sbyte)info.binary[info.currIdx - 1];
|
||||
info.Add(value);
|
||||
}
|
||||
private static void UnpackFixMap(SAXInfo info)
|
||||
{
|
||||
byte length = info.binary[info.currIdx - 1];
|
||||
length &= 0x0F;
|
||||
|
||||
ContainerInfo container = new ContainerInfo(true, (int)length);
|
||||
info.AddContainer(container);
|
||||
|
||||
for (ushort i = 0; i < length * 2; i++)
|
||||
UnpackNext(info);
|
||||
|
||||
info.PopContainer();
|
||||
}
|
||||
private static void UnpackFixArray(SAXInfo info)
|
||||
{
|
||||
byte length = info.binary[info.currIdx - 1];
|
||||
length &= 0x0F;
|
||||
|
||||
ContainerInfo container = new ContainerInfo(false, (int)length);
|
||||
info.AddContainer(container);
|
||||
|
||||
for (byte i = 0; i < length; i++)
|
||||
UnpackNext(info);
|
||||
|
||||
info.PopContainer();
|
||||
}
|
||||
private static void UnpackFixStr(SAXInfo info)
|
||||
{
|
||||
byte length = info.binary[info.currIdx - 1];
|
||||
length &= 0x1F;
|
||||
|
||||
UnpackString(info, length);
|
||||
}
|
||||
private static void UnpackNegativeFixInt(SAXInfo info)
|
||||
{
|
||||
byte value = info.binary[info.currIdx - 1];
|
||||
info.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9de0a1029a27403da718c42c6440c19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
## FPNN MsgPack Implement
|
||||
|
||||
* **[msgpack SPEC](https://github.com/msgpack/msgpack/blob/master/spec.md)**
|
||||
* **[Project Home](https://github.com/highras/msgpack-csharp)**
|
||||
|
||||
### Compatibility Version:
|
||||
|
||||
C# .Net Standard 2.0
|
||||
|
||||
### For Packer:
|
||||
|
||||
* input kinds:
|
||||
|
||||
null, bool, sbyte, byte, short, ushort, Int32, UInt32, Int64, Uint64, float, double, string
|
||||
|
||||
Decimal, Tuple
|
||||
|
||||
byte[], DateTime, IEnumerable (such as List, Dictionary, List\<object\>, Dictionary\<object, object\>, ...)
|
||||
|
||||
* usage:
|
||||
|
||||
using com.fpnn.msgpack;
|
||||
void MsgPacker.Pack(Stream stream, Object obj);
|
||||
|
||||
|
||||
### For Unpacker:
|
||||
|
||||
* output kinds:
|
||||
|
||||
Object, which maybe the following kinds:
|
||||
|
||||
null, bool, sbyte, byte, short, ushort, Int32, UInt32, Int64, Uint64, float, double, string
|
||||
|
||||
byte[], DateTime, List\<object\>, Dictionary\<object, object\>
|
||||
|
||||
* usage:
|
||||
|
||||
using com.fpnn.msgpack;
|
||||
Dictionary<Object, Object> MsgUnpacker.Unpack(byte[] binary);
|
||||
|
||||
Dictionary<Object, Object> MsgUnpacker.Unpack(byte[] binary, int offset, int length = 0);
|
||||
|
||||
//-- unpack one object.
|
||||
Object MsgUnpacker.Unpack(byte[] binary, int offset, out int endOffset);
|
||||
|
||||
|
||||
### Exception:
|
||||
|
||||
using com.fpnn.msgpack;
|
||||
public class MsgPackException: Exception;
|
||||
public class UnsupportedTypeException : MsgPackException;
|
||||
public class UnrecognizedDataException : MsgPackException;
|
||||
public class InsufficientException : MsgPackException;
|
||||
public class InvalidDataException : MsgPackException;
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 730f8ecda09534c29b067b8300af136b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 826831a92fb244951bde24d80cd58039
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace com.fpnn.proto
|
||||
{
|
||||
public class Answer : Message
|
||||
{
|
||||
private static readonly byte[] FPNNBasicAnswerHeader = { 0x46, 0x50, 0x4e, 0x4e, 0x1, 0x80, 0x2 };
|
||||
private static readonly byte[] fakePayloadLength = { 0, 0, 0, 0 };
|
||||
|
||||
private bool errorAnswer;
|
||||
private UInt32 seqNum;
|
||||
|
||||
public Answer(Quest quest): this(quest.SeqNum())
|
||||
{
|
||||
}
|
||||
|
||||
public Answer(UInt32 seqNum)
|
||||
{
|
||||
errorAnswer = false;
|
||||
this.seqNum = seqNum;
|
||||
}
|
||||
|
||||
public Answer(UInt32 seqNum, bool error, Dictionary<object, object> payload)
|
||||
: base(payload)
|
||||
{
|
||||
errorAnswer = error;
|
||||
this.seqNum = seqNum;
|
||||
}
|
||||
|
||||
public UInt32 SeqNum()
|
||||
{
|
||||
return seqNum;
|
||||
}
|
||||
|
||||
public bool IsException()
|
||||
{
|
||||
return errorAnswer;
|
||||
}
|
||||
|
||||
public int ErrorCode()
|
||||
{
|
||||
return (int)Get("code", 0);
|
||||
}
|
||||
|
||||
public string Ex()
|
||||
{
|
||||
return (string)Get("ex", "");
|
||||
}
|
||||
|
||||
public void FillErrorCode(int code)
|
||||
{
|
||||
errorAnswer = true;
|
||||
Param("code", code);
|
||||
}
|
||||
|
||||
public void FillErrorInfo(int code, string ex)
|
||||
{
|
||||
errorAnswer = true;
|
||||
Param("code", code);
|
||||
Param("ex", ex);
|
||||
}
|
||||
|
||||
private void BuildFPNNPackage(Stream stream)
|
||||
{
|
||||
stream.Write(FPNNBasicAnswerHeader, 0, 7);
|
||||
if (errorAnswer)
|
||||
stream.WriteByte(0x1);
|
||||
else
|
||||
stream.WriteByte(0x0);
|
||||
|
||||
//-- payload size
|
||||
stream.Write(fakePayloadLength, 0, 4);
|
||||
|
||||
//-- seq num
|
||||
byte[] seqBuffer = BitConverter.GetBytes(seqNum);
|
||||
|
||||
if (BitConverter.IsLittleEndian == false)
|
||||
Array.Reverse(seqBuffer);
|
||||
|
||||
stream.Write(seqBuffer, 0, 4);
|
||||
}
|
||||
|
||||
|
||||
new public byte[] Raw()
|
||||
{
|
||||
byte[] rawData;
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
BuildFPNNPackage(stream);
|
||||
base.Raw(stream);
|
||||
rawData = stream.ToArray();
|
||||
}
|
||||
|
||||
Int32 payloadLength = rawData.Length - 16; //-- 16: Answer heander length.
|
||||
|
||||
byte[] payloadLengthBuffer = BitConverter.GetBytes(payloadLength);
|
||||
|
||||
if (BitConverter.IsLittleEndian == false)
|
||||
Array.Reverse(payloadLengthBuffer);
|
||||
|
||||
Array.Copy(payloadLengthBuffer, 0, rawData, 8, 4);
|
||||
|
||||
return rawData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b355061098e6549cc81fd05e63407130
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using com.fpnn.msgpack;
|
||||
|
||||
namespace com.fpnn.proto
|
||||
{
|
||||
public class Message
|
||||
{
|
||||
private Dictionary<object, object> payload;
|
||||
|
||||
public Message()
|
||||
{
|
||||
payload = new Dictionary<object, object>();
|
||||
}
|
||||
|
||||
public Message(Dictionary<object, object> payload)
|
||||
{
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
//-----------------[ Data Accessing Functions ]-------------------
|
||||
public void Param(string key, object value)
|
||||
{
|
||||
payload.Add(key, value);
|
||||
}
|
||||
|
||||
public object Get(string key, object defValue = null)
|
||||
{
|
||||
if (payload.TryGetValue(key, out object value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return defValue;
|
||||
}
|
||||
|
||||
public T Get<T>(string key, T defValue)
|
||||
{
|
||||
if (payload.TryGetValue(key, out object value))
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
return defValue;
|
||||
}
|
||||
|
||||
public object Want(string key)
|
||||
{
|
||||
return payload[key];
|
||||
}
|
||||
|
||||
public T Want<T>(string key)
|
||||
{
|
||||
object obj = payload[key];
|
||||
return (T)Convert.ChangeType(obj, typeof(T));
|
||||
}
|
||||
|
||||
//-----------------[ To Bytes Array Functions ]-------------------
|
||||
protected void Raw(Stream stream)
|
||||
{
|
||||
MsgPacker.Pack(stream, payload);
|
||||
}
|
||||
|
||||
public byte[] Raw()
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
MsgPacker.Pack(stream, payload);
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f32b2cf0b56c4ac692d21d23aeab592
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace com.fpnn.proto
|
||||
{
|
||||
public class Quest : Message
|
||||
{
|
||||
private static readonly byte[] FPNNBasicHeader = { 0x46, 0x50, 0x4e, 0x4e, 0x1, 0x80 };
|
||||
private static readonly byte[] fakePayloadLength = { 0, 0, 0, 0 };
|
||||
|
||||
private UInt32 seqNum;
|
||||
private bool isOneWay;
|
||||
private String method;
|
||||
|
||||
private static class SeqNumGenerator
|
||||
{
|
||||
static private object interLocker;
|
||||
static private int count;
|
||||
|
||||
static SeqNumGenerator()
|
||||
{
|
||||
interLocker = new object();
|
||||
count = (int)(GetCurrentMilliseconds() % 1000000);
|
||||
}
|
||||
|
||||
static public Int64 GetCurrentMilliseconds()
|
||||
{
|
||||
DateTime originDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
TimeSpan span = DateTime.UtcNow - originDateTime;
|
||||
return (Int64)Math.Floor(span.TotalMilliseconds);
|
||||
}
|
||||
|
||||
static public int Gen()
|
||||
{
|
||||
lock (interLocker)
|
||||
{
|
||||
return ++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Quest(string method): this(method, false)
|
||||
{
|
||||
}
|
||||
|
||||
public Quest(string method, bool isOneWay)
|
||||
{
|
||||
this.method = method;
|
||||
this.isOneWay = isOneWay;
|
||||
seqNum = 0;
|
||||
}
|
||||
|
||||
public Quest(string method, bool isOneWay, UInt32 seqNum, Dictionary<object, object> payload)
|
||||
: base(payload)
|
||||
{
|
||||
this.method = method;
|
||||
this.isOneWay = isOneWay;
|
||||
this.seqNum = seqNum;
|
||||
}
|
||||
|
||||
public UInt32 SeqNum()
|
||||
{
|
||||
return seqNum;
|
||||
}
|
||||
|
||||
public String Method()
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
public bool IsOneWay()
|
||||
{
|
||||
return isOneWay;
|
||||
}
|
||||
|
||||
public bool IsTwoWay()
|
||||
{
|
||||
return !isOneWay;
|
||||
}
|
||||
|
||||
private int BuildFPNNPackage(Stream stream)
|
||||
{
|
||||
UTF8Encoding utf8Encoding = new UTF8Encoding(); //-- NO BOM.
|
||||
byte[] methodData = utf8Encoding.GetBytes(method);
|
||||
|
||||
if (seqNum == 0)
|
||||
seqNum = (UInt32)SeqNumGenerator.Gen();
|
||||
|
||||
stream.Write(FPNNBasicHeader, 0, 6);
|
||||
if (isOneWay)
|
||||
stream.WriteByte(0x0);
|
||||
else
|
||||
stream.WriteByte(0x1);
|
||||
|
||||
stream.WriteByte((byte)methodData.Length);
|
||||
|
||||
//-- payload size
|
||||
stream.Write(fakePayloadLength, 0, 4);
|
||||
|
||||
//-- seq num
|
||||
if (isOneWay == false)
|
||||
{
|
||||
byte[] seqBuffer = BitConverter.GetBytes(seqNum);
|
||||
|
||||
if (BitConverter.IsLittleEndian == false)
|
||||
Array.Reverse(seqBuffer);
|
||||
|
||||
stream.Write(seqBuffer, 0, 4);
|
||||
}
|
||||
|
||||
//-- method
|
||||
stream.Write(methodData, 0, methodData.Length);
|
||||
|
||||
return methodData.Length;
|
||||
}
|
||||
|
||||
new public byte[] Raw()
|
||||
{
|
||||
byte[] rawData;
|
||||
int methodUTF8Length;
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
methodUTF8Length = BuildFPNNPackage(stream);
|
||||
base.Raw(stream);
|
||||
rawData = stream.ToArray();
|
||||
}
|
||||
|
||||
Int32 payloadLength = rawData.Length - 12 - methodUTF8Length;
|
||||
if (!isOneWay)
|
||||
payloadLength -= 4;
|
||||
|
||||
byte[] payloadLengthBuffer = BitConverter.GetBytes(payloadLength);
|
||||
|
||||
if (BitConverter.IsLittleEndian == false)
|
||||
Array.Reverse(payloadLengthBuffer);
|
||||
|
||||
Array.Copy(payloadLengthBuffer, 0, rawData, 8, 4);
|
||||
|
||||
return rawData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6bdb7f6b5f826456b9c33fed503c27dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user