备份CatanBuilding瘦身独立工程

This commit is contained in:
JSD\13999
2026-05-26 16:15:54 +08:00
commit 2d0e6a61b7
12001 changed files with 2431925 additions and 0 deletions

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e58c58107c454801a373f00fa94e3e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20db4fa13ef9a4ac185ee098ce20b9cc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5b5292e70c9e04ac980bcf0e98619904
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
namespace com.fpnn
{
public static partial class ClientEngine
{
/*
static partial void PlatformInit()
{
}
static partial void PlatformUninit()
{
}
*/
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8415179750e914bc49cf91b97cf509d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ce8091be660e14f26897422b2c8249ec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 62c99441214ec484f8546f544497416e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 411914f28889547d2a19e66e5761c079
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 42152b3f9a08f42e4bad736798b3a6a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 320517d4713ce44bca818220027f7110
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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.");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1e915f7872174de6bcc1cdc8208d02c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fadd430f72bbc48de8cae11f2b79b9a1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a41e8ca65b9e3415f9a4b0b2407a0aca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69a9d33ada7b746d383d500683ccea60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fe1ce30ab96834f75b384c6f95ce8513
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3eb5149f8432d46c9b5fd967c04837cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 67b78d9afdd27429c8705ad6b904740a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f777f23eee1ce44f8b5a9f635069c4d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 66749982ee02845f4992ea52fe113376
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 623c0247982494d8ebd41db778b9532c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1cbd313282dcd4053aa29bb130a65452
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 90c2b19d237af4d54ab342ee0ce9ecb7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9de0a1029a27403da718c42c6440c19
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 730f8ecda09534c29b067b8300af136b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 826831a92fb244951bde24d80cd58039
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b355061098e6549cc81fd05e63407130
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f32b2cf0b56c4ac692d21d23aeab592
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6bdb7f6b5f826456b9c33fed503c27dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: