备份CatanBuilding瘦身独立工程
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
namespace com.fpnn.common
|
||||
{
|
||||
public interface ErrorRecorder
|
||||
{
|
||||
void RecordError(Exception e);
|
||||
void RecordError(string message);
|
||||
void RecordError(string message, Exception e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3eb5149f8432d46c9b5fd967c04837cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,640 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace com.fpnn.common
|
||||
{
|
||||
public static class Json
|
||||
{
|
||||
public static object Parse(string json)
|
||||
{
|
||||
JsonParser parser = JsonParser.Create(json);
|
||||
return parser.Parse();
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> ParseObject(string json)
|
||||
{
|
||||
JsonParser parser = JsonParser.Create(json);
|
||||
return parser.ParseObject();
|
||||
}
|
||||
|
||||
public static string ToString(object obj)
|
||||
{
|
||||
JsonStringify js = new JsonStringify();
|
||||
return js.Stringify(obj);
|
||||
}
|
||||
}
|
||||
|
||||
//============================[ Exception ]============================//
|
||||
public class JsonException : Exception
|
||||
{
|
||||
public JsonException(string message) : base(message) { }
|
||||
public JsonException(String message, Exception ex) : base(message, ex) { }
|
||||
}
|
||||
|
||||
public class InvalidJsonException : JsonException
|
||||
{
|
||||
public InvalidJsonException(string message) : base(message) { }
|
||||
public InvalidJsonException(string message, Exception ex) : base(message, ex) { }
|
||||
}
|
||||
|
||||
public class JsonTypeException : JsonException
|
||||
{
|
||||
public JsonTypeException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
//============================[ Json Parser ]============================//
|
||||
|
||||
internal enum JsonElementType
|
||||
{
|
||||
Empty,
|
||||
Null,
|
||||
Boolean,
|
||||
String,
|
||||
Int64,
|
||||
UInt64,
|
||||
Double,
|
||||
Array,
|
||||
Dictionary,
|
||||
}
|
||||
|
||||
internal delegate void JsonParseSignDelegate(JsonParser parser);
|
||||
|
||||
internal class JsonParser
|
||||
{
|
||||
class JsonElement
|
||||
{
|
||||
public object element;
|
||||
public JsonElementType type;
|
||||
}
|
||||
|
||||
static private readonly Dictionary<char, JsonParseSignDelegate> signDelegateMap;
|
||||
static private readonly Dictionary<char, ushort> hexTable;
|
||||
static private readonly HashSet<char> numericalChars;
|
||||
|
||||
private readonly string json;
|
||||
private int idx;
|
||||
private string key;
|
||||
private bool wantKey;
|
||||
private bool wantValue;
|
||||
private bool wantComma;
|
||||
private bool wantSemicolon;
|
||||
|
||||
private readonly Stack<JsonElement> elementStack;
|
||||
|
||||
private object parseResult;
|
||||
private JsonElementType resultType;
|
||||
|
||||
static JsonParser()
|
||||
{
|
||||
signDelegateMap = new Dictionary<char, JsonParseSignDelegate>
|
||||
{
|
||||
{ '{', EnterObject },
|
||||
{ '}', ExitObject },
|
||||
{ '[', EnterArray },
|
||||
{ ']', ExitArray },
|
||||
{ ',', ProcessComma },
|
||||
{ ':', ProcessSemicolon },
|
||||
{ '"', ProcessString },
|
||||
};
|
||||
|
||||
hexTable = new Dictionary<char, ushort>
|
||||
{
|
||||
{ '0', 0 },
|
||||
{ '1', 1 },
|
||||
{ '2', 2 },
|
||||
{ '3', 3 },
|
||||
{ '4', 4 },
|
||||
{ '5', 5 },
|
||||
{ '6', 6 },
|
||||
{ '7', 7 },
|
||||
{ '8', 8 },
|
||||
{ '9', 9 },
|
||||
|
||||
{ 'a', 10 },
|
||||
{ 'b', 11 },
|
||||
{ 'c', 12 },
|
||||
{ 'd', 13 },
|
||||
{ 'e', 14 },
|
||||
{ 'f', 15 },
|
||||
|
||||
{ 'A', 10 },
|
||||
{ 'B', 11 },
|
||||
{ 'C', 12 },
|
||||
{ 'D', 13 },
|
||||
{ 'E', 14 },
|
||||
{ 'F', 15 },
|
||||
};
|
||||
|
||||
numericalChars = new HashSet<char>
|
||||
{
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'+', '-', '.', 'E', 'e'
|
||||
};
|
||||
}
|
||||
|
||||
private static void EnterObject(JsonParser parser)
|
||||
{
|
||||
parser.EnterObject();
|
||||
}
|
||||
private static void ExitObject(JsonParser parser)
|
||||
{
|
||||
parser.ExitObject();
|
||||
}
|
||||
private static void EnterArray(JsonParser parser)
|
||||
{
|
||||
parser.EnterArray();
|
||||
}
|
||||
private static void ExitArray(JsonParser parser)
|
||||
{
|
||||
parser.ExitArray();
|
||||
}
|
||||
private static void ProcessComma(JsonParser parser)
|
||||
{
|
||||
parser.ProcessComma();
|
||||
}
|
||||
private static void ProcessSemicolon(JsonParser parser)
|
||||
{
|
||||
parser.ProcessSemicolon();
|
||||
}
|
||||
private static void ProcessString(JsonParser parser)
|
||||
{
|
||||
parser.ProcessString();
|
||||
}
|
||||
|
||||
public static JsonParser Create(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
throw new InvalidJsonException("Json Parser: content error. Null or empty content.");
|
||||
|
||||
JsonParser parser;
|
||||
|
||||
try
|
||||
{
|
||||
string normalizedString = json;
|
||||
if (!json.IsNormalized())
|
||||
normalizedString = json.Normalize();
|
||||
|
||||
parser = new JsonParser(normalizedString);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: content error. Include invalid Unicode.", e);
|
||||
}
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
//---------------------[ Instance Parts ]--------------------------//
|
||||
private JsonParser(string jsonString)
|
||||
{
|
||||
json = jsonString;
|
||||
idx = 0;
|
||||
|
||||
elementStack = new Stack<JsonElement>();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
key = string.Empty;
|
||||
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = false;
|
||||
wantSemicolon = false;
|
||||
|
||||
elementStack.Clear();
|
||||
|
||||
parseResult = null;
|
||||
resultType = JsonElementType.Empty;
|
||||
}
|
||||
|
||||
private void ParseCore()
|
||||
{
|
||||
Reset();
|
||||
|
||||
while (idx < json.Length && parseResult == null)
|
||||
{
|
||||
if (Char.IsWhiteSpace(json[idx]))
|
||||
{
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (signDelegateMap.TryGetValue(json[idx], out JsonParseSignDelegate method))
|
||||
method(this);
|
||||
else
|
||||
GeneralProcess();
|
||||
}
|
||||
}
|
||||
|
||||
public object Parse()
|
||||
{
|
||||
ParseCore();
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
|
||||
public Dictionary<string, object> ParseObject()
|
||||
{
|
||||
ParseCore();
|
||||
if (resultType == JsonElementType.Dictionary)
|
||||
return (Dictionary<string, object>)parseResult;
|
||||
|
||||
throw new JsonTypeException("Json Parser: want the Dictionary type, but the parsed type is " + resultType.ToString("G") + ".");
|
||||
}
|
||||
|
||||
private void FillKey(string value)
|
||||
{
|
||||
key = value;
|
||||
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = false;
|
||||
wantSemicolon = true;
|
||||
}
|
||||
|
||||
private void FillValue(object obj, JsonElementType type)
|
||||
{
|
||||
Dictionary<string, object> dict = (Dictionary<string, object>)elementStack.Peek().element;
|
||||
try
|
||||
{
|
||||
dict.Add(key, obj);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: reduplicated key: \"" + key + "\", offset " + idx, e);
|
||||
}
|
||||
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = true;
|
||||
wantSemicolon = false;
|
||||
}
|
||||
|
||||
private void InsertNode(object obj, JsonElementType type)
|
||||
{
|
||||
int stackCount = elementStack.Count;
|
||||
if (stackCount > 0)
|
||||
{
|
||||
if (wantValue)
|
||||
FillValue(obj, type);
|
||||
else
|
||||
{
|
||||
List<object> list = (List<object>)elementStack.Peek().element;
|
||||
list.Add(obj);
|
||||
|
||||
wantComma = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == JsonElementType.Array || type == JsonElementType.Dictionary)
|
||||
{
|
||||
elementStack.Push(new JsonElement()
|
||||
{
|
||||
element = obj,
|
||||
type = type
|
||||
});
|
||||
}
|
||||
else if (stackCount == 0)
|
||||
{
|
||||
parseResult = obj;
|
||||
resultType = type;
|
||||
}
|
||||
}
|
||||
|
||||
private void FinishNode()
|
||||
{
|
||||
JsonElement element = elementStack.Pop();
|
||||
|
||||
if (elementStack.Count == 0)
|
||||
{
|
||||
parseResult = element.element;
|
||||
resultType = element.type;
|
||||
}
|
||||
else
|
||||
{
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = true;
|
||||
wantSemicolon = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSlash(ref StringBuilder stringBuilder)
|
||||
{
|
||||
idx++;
|
||||
if (idx >= json.Length)
|
||||
throw new InvalidJsonException("Json Parser: content error, json truncated after '\\'.");
|
||||
|
||||
switch (json[idx])
|
||||
{
|
||||
case '"':
|
||||
stringBuilder.Append('"');
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
stringBuilder.Append('\\');
|
||||
break;
|
||||
|
||||
case '/':
|
||||
stringBuilder.Append('/');
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
stringBuilder.Append('\b');
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
stringBuilder.Append('\f');
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
stringBuilder.Append('\n');
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
stringBuilder.Append('\r');
|
||||
break;
|
||||
|
||||
case 't':
|
||||
stringBuilder.Append('\t');
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
if (idx + 5 >= json.Length)
|
||||
throw new InvalidJsonException("Json Parser: content error, json truncated after '\\u'.");
|
||||
|
||||
idx++;
|
||||
ushort value = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (hexTable.TryGetValue(json[idx+i], out ushort v))
|
||||
{
|
||||
value <<= 4;
|
||||
value += v;
|
||||
}
|
||||
else
|
||||
throw new InvalidJsonException("Json Parser: content error, invalid hex number for '\\u'. Offset " + idx);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
char c = Convert.ToChar(value);
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
catch (OverflowException e)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: content error, invalid unicode value for '\\u'. Offset " + idx, e);
|
||||
}
|
||||
|
||||
idx += 4;
|
||||
return;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
private string FetchString()
|
||||
{
|
||||
idx++;
|
||||
int startIdx = idx;
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
while (idx < json.Length)
|
||||
{
|
||||
if (json[idx] == '\\')
|
||||
{
|
||||
int count = idx - startIdx;
|
||||
if (count > 0)
|
||||
stringBuilder.Append(json, startIdx, count);
|
||||
|
||||
ProcessSlash(ref stringBuilder);
|
||||
startIdx = idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (json[idx] == '"')
|
||||
{
|
||||
int count = idx - startIdx;
|
||||
if (count > 0)
|
||||
stringBuilder.Append(json, startIdx, count);
|
||||
|
||||
idx++;
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
throw new InvalidJsonException("Json Parser: content error, json truncated in string value.");
|
||||
}
|
||||
|
||||
private void EnterObject()
|
||||
{
|
||||
if (wantKey || wantSemicolon || wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error, '{' at improper place. Offset " + idx);
|
||||
|
||||
InsertNode(new Dictionary<string, object>(), JsonElementType.Dictionary);
|
||||
|
||||
wantKey = true;
|
||||
wantValue = false;
|
||||
wantComma = false;
|
||||
wantSemicolon = false;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
private void ExitObject()
|
||||
{
|
||||
if (wantValue || wantSemicolon || elementStack.Count == 0 || elementStack.Peek().type != JsonElementType.Dictionary)
|
||||
throw new InvalidJsonException("Json Parser: content error, '}' at improper place. Offset " + idx);
|
||||
|
||||
idx++;
|
||||
FinishNode();
|
||||
}
|
||||
|
||||
private void EnterArray()
|
||||
{
|
||||
if (wantKey || wantSemicolon || wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error, '[' at improper place. Offset " + idx);
|
||||
|
||||
InsertNode(new List<object>(), JsonElementType.Array);
|
||||
|
||||
wantKey = false;
|
||||
wantValue = false;
|
||||
wantComma = false;
|
||||
wantSemicolon = false;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
private void ExitArray()
|
||||
{
|
||||
if (wantValue || wantSemicolon || elementStack.Count == 0 || elementStack.Peek().type != JsonElementType.Array)
|
||||
throw new InvalidJsonException("Json Parser: content error, ']' at improper place. Offset " + idx);
|
||||
|
||||
idx++;
|
||||
FinishNode();
|
||||
}
|
||||
|
||||
private void ProcessComma()
|
||||
{
|
||||
if (!wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error. ',' at error position. Offset " + idx);
|
||||
|
||||
wantComma = false;
|
||||
if (elementStack.Peek().type == JsonElementType.Dictionary)
|
||||
wantKey = true;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
private void ProcessSemicolon()
|
||||
{
|
||||
if (!wantSemicolon)
|
||||
throw new InvalidJsonException("Json Parser: content error. ':' at error position. Offset " + idx);
|
||||
|
||||
wantSemicolon = false;
|
||||
wantValue = true;
|
||||
idx++;
|
||||
}
|
||||
|
||||
private void ProcessString()
|
||||
{
|
||||
if (wantSemicolon || wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error. Require ':' or ','. Offset " + idx);
|
||||
|
||||
string value = FetchString();
|
||||
|
||||
if (wantKey)
|
||||
FillKey(value);
|
||||
else
|
||||
InsertNode(value, JsonElementType.String);
|
||||
}
|
||||
|
||||
private void GeneralProcess()
|
||||
{
|
||||
if (wantKey || wantSemicolon || wantComma)
|
||||
throw new InvalidJsonException("Json Parser: content error. Require '\"' or ':' or ','. Offset " + idx);
|
||||
|
||||
if (idx + 3 < json.Length)
|
||||
{
|
||||
string tmp = json.Substring(idx, 4);
|
||||
if (string.Equals(tmp, "true", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 4;
|
||||
InsertNode(true, JsonElementType.Boolean);
|
||||
return;
|
||||
}
|
||||
if (string.Equals(tmp, "null", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 4;
|
||||
InsertNode(null, JsonElementType.Null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx + 4 < json.Length)
|
||||
{
|
||||
string tmp = json.Substring(idx, 5);
|
||||
if (string.Equals(tmp, "false", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 5;
|
||||
InsertNode(false, JsonElementType.Boolean);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessNumber();
|
||||
}
|
||||
|
||||
private void ProcessNumber()
|
||||
{
|
||||
int dot = 0;
|
||||
int e = 0;
|
||||
int startIndex = idx;
|
||||
|
||||
while (idx < json.Length && numericalChars.Contains(json[idx]))
|
||||
{
|
||||
if (json[idx] == '.')
|
||||
dot++;
|
||||
else if (json[idx] == 'e' || json[idx] == 'E')
|
||||
e++;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (startIndex == idx)
|
||||
{
|
||||
if (idx + 7 < json.Length)
|
||||
{
|
||||
string tmp = json.Substring(idx, 8);
|
||||
if (string.Equals(tmp, "Infinity", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 8;
|
||||
InsertNode(Double.PositiveInfinity, JsonElementType.Double);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx + 2 < json.Length)
|
||||
{
|
||||
string tmp = json.Substring(idx, 3);
|
||||
if (string.Equals(tmp, "nan", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 3;
|
||||
InsertNode(Double.NaN, JsonElementType.Double);
|
||||
return;
|
||||
}
|
||||
if (string.Equals(tmp, "inf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
idx += 3;
|
||||
InsertNode(Double.PositiveInfinity, JsonElementType.Double);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidJsonException("Json Parser: content error. Unparseable content. Offset " + idx);
|
||||
}
|
||||
|
||||
if (e > 1 || dot > 1)
|
||||
throw new InvalidJsonException("Json Parser: content error. Unparseable content, invalid number. Offset " + idx);
|
||||
|
||||
string numberString = json.Substring(startIndex, idx - startIndex);
|
||||
|
||||
if (e > 0 || dot > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
double value = Convert.ToDouble(numberString);
|
||||
InsertNode(value, JsonElementType.Double);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: content error. Invalid number. Offset " + idx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
long value = Convert.ToInt64(numberString);
|
||||
InsertNode(value, JsonElementType.Int64);
|
||||
return;
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
UInt64 value = Convert.ToUInt64(numberString);
|
||||
InsertNode(value, JsonElementType.UInt64);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidJsonException("Json Parser: content error. Invalid number. Offset " + idx, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67b78d9afdd27429c8705ad6b904740a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,322 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace com.fpnn.common
|
||||
{
|
||||
internal static class JsonStringEscaper
|
||||
{
|
||||
public delegate void CharEscape(StringBuilder sb, char c);
|
||||
|
||||
private static readonly Dictionary<char, CharEscape> _charEscapeDict;
|
||||
|
||||
static JsonStringEscaper()
|
||||
{
|
||||
_charEscapeDict = new Dictionary<char, CharEscape>
|
||||
{
|
||||
{ '\\', Slash },
|
||||
{ '"', QuotationMarks },
|
||||
{ '\b', SpecialChars },
|
||||
{ '\f', SpecialChars },
|
||||
{ '\n', SpecialChars },
|
||||
{ '\r', SpecialChars },
|
||||
{ '\t', SpecialChars },
|
||||
};
|
||||
}
|
||||
|
||||
static public void Escape(StringBuilder sb, string str)
|
||||
{
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
char c = str[i];
|
||||
if (_charEscapeDict.TryGetValue(c, out CharEscape escapeFunc))
|
||||
{
|
||||
escapeFunc(sb, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
ushort value = Convert.ToUInt16(c);
|
||||
|
||||
if (value > 0x1f && value < 0x7f) //-- ASCII visible chars
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("\\u");
|
||||
sb.Append(value.ToString("x4"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private void SpecialChars(StringBuilder sb, char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\b': sb.Append("\\b"); break;
|
||||
case '\f': sb.Append("\\f"); break;
|
||||
case '\n': sb.Append("\\n"); break;
|
||||
case '\r': sb.Append("\\r"); break;
|
||||
case '\t': sb.Append("\\t"); break;
|
||||
}
|
||||
}
|
||||
|
||||
static private void Slash(StringBuilder sb, char c)
|
||||
{
|
||||
sb.Append("\\\\");
|
||||
}
|
||||
|
||||
static private void QuotationMarks(StringBuilder sb, char c)
|
||||
{
|
||||
sb.Append("\\\"");
|
||||
}
|
||||
}
|
||||
|
||||
//============================[ Exception ]============================//
|
||||
|
||||
public class UnsupportedTypeException : JsonException
|
||||
{
|
||||
public UnsupportedTypeException(string message) : base(message) { }
|
||||
|
||||
static public UnsupportedTypeException Create(Object obj)
|
||||
{
|
||||
string typeFullName = obj.GetType().FullName;
|
||||
return new UnsupportedTypeException("FPJson unsupported type: " + typeFullName);
|
||||
}
|
||||
|
||||
static public UnsupportedTypeException DictionaryKey(Object obj)
|
||||
{
|
||||
string typeFullName = obj.GetType().FullName;
|
||||
return new UnsupportedTypeException("FPJson unsupported object key type: " + typeFullName);
|
||||
}
|
||||
}
|
||||
|
||||
//============================[ Json Stringify ]============================//
|
||||
|
||||
internal class JsonStringify
|
||||
{
|
||||
public delegate void Serialize(JsonStringify js, object obj);
|
||||
|
||||
private static readonly Dictionary<string, Serialize> _serializeDict;
|
||||
|
||||
static JsonStringify()
|
||||
{
|
||||
_serializeDict = new Dictionary<string, Serialize>
|
||||
{
|
||||
{ "System.Boolean", SerializeBoolean },
|
||||
|
||||
{ "System.Decimal", SerializeDecimal },
|
||||
{ "System.Double", SerializeDouble },
|
||||
{ "System.Single", SerializeFloat },
|
||||
|
||||
{ "System.SByte", SerializeInteger },
|
||||
{ "System.Int16", SerializeInteger },
|
||||
{ "System.Int32", SerializeInteger },
|
||||
{ "System.Int64", SerializeInteger },
|
||||
|
||||
{ "System.Byte", SerializeUInteger },
|
||||
{ "System.Char", SerializeUInteger },
|
||||
{ "System.UInt16", SerializeUInteger },
|
||||
{ "System.UInt32", SerializeUInteger },
|
||||
{ "System.UInt64", SerializeUInteger },
|
||||
|
||||
{ "System.String", SerializeString },
|
||||
{ "System.Tuple", SerializeTuple },
|
||||
{ "System.DateTime", SerializeTimestamp }
|
||||
};
|
||||
}
|
||||
|
||||
//-- Instance Methods
|
||||
|
||||
private StringBuilder sb;
|
||||
|
||||
public JsonStringify()
|
||||
{
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
public string Stringify(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
SerializeNull(this);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
string typeFullName = obj.GetType().FullName;
|
||||
int idx = typeFullName.IndexOf('`');
|
||||
if (idx != -1)
|
||||
{
|
||||
typeFullName = typeFullName.Substring(0, idx);
|
||||
}
|
||||
|
||||
if (_serializeDict.TryGetValue(typeFullName, out Serialize serializer))
|
||||
{
|
||||
serializer(this, obj);
|
||||
}
|
||||
else if (obj is IEnumerable)
|
||||
{
|
||||
SerializeIEnumerable(this, obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw UnsupportedTypeException.Create(obj);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
static private void SerializeBoolean(JsonStringify js, object obj)
|
||||
{
|
||||
Boolean v = (bool)obj;
|
||||
if (v == true)
|
||||
js.sb.Append("true");
|
||||
else
|
||||
js.sb.Append("false");
|
||||
}
|
||||
|
||||
static private void SerializeDecimal(JsonStringify js, object obj)
|
||||
{
|
||||
Decimal dec = (Decimal)obj;
|
||||
js.sb.Append(dec);
|
||||
}
|
||||
|
||||
static private void SerializeDouble(JsonStringify js, object obj)
|
||||
{
|
||||
double value = (double)obj;
|
||||
js.sb.Append(value);
|
||||
}
|
||||
|
||||
static private void SerializeFloat(JsonStringify js, object obj)
|
||||
{
|
||||
float value = (float)obj;
|
||||
js.sb.Append(value);
|
||||
}
|
||||
|
||||
static private void SerializeInteger(JsonStringify js, object obj)
|
||||
{
|
||||
Int64 value = (Int64)Convert.ChangeType(obj, TypeCode.Int64);
|
||||
js.sb.Append(value);
|
||||
}
|
||||
|
||||
static private void SerializeUInteger(JsonStringify js, object obj)
|
||||
{
|
||||
UInt64 value = (UInt64)Convert.ChangeType(obj, TypeCode.UInt64);
|
||||
js.sb.Append(value);
|
||||
}
|
||||
|
||||
static private void SerializeString(JsonStringify js, object obj)
|
||||
{
|
||||
string str = (string)obj;
|
||||
|
||||
js.sb.Append("\"");
|
||||
JsonStringEscaper.Escape(js.sb, str);
|
||||
js.sb.Append("\"");
|
||||
}
|
||||
|
||||
static private void SerializeTuple(JsonStringify js, object obj)
|
||||
{
|
||||
Type objType = obj.GetType();
|
||||
var props = objType.GetProperties();
|
||||
|
||||
bool isFirst = true;
|
||||
js.sb.Append("[");
|
||||
|
||||
foreach (System.Reflection.PropertyInfo prop in props)
|
||||
{
|
||||
if (isFirst)
|
||||
isFirst = false;
|
||||
else
|
||||
js.sb.Append(",");
|
||||
|
||||
object o = prop.GetValue(obj, null);
|
||||
js.Stringify(o);
|
||||
}
|
||||
|
||||
js.sb.Append("]");
|
||||
}
|
||||
static private void SerializeTimestamp(JsonStringify js, object obj)
|
||||
{
|
||||
DateTime userDate = (DateTime)obj;
|
||||
var univDateTime = userDate.ToUniversalTime();
|
||||
|
||||
js.sb.Append("\"");
|
||||
js.sb.Append(univDateTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
js.sb.Append("\"");
|
||||
}
|
||||
|
||||
static private void SerializeNull(JsonStringify js)
|
||||
{
|
||||
js.sb.Append("null");
|
||||
}
|
||||
|
||||
static private void SerializeArray(JsonStringify js, object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
bool isFirst = true;
|
||||
js.sb.Append("[");
|
||||
|
||||
while (it.MoveNext())
|
||||
{
|
||||
if (isFirst)
|
||||
isFirst = false;
|
||||
else
|
||||
js.sb.Append(",");
|
||||
|
||||
object o = it.Current;
|
||||
js.Stringify(o);
|
||||
}
|
||||
|
||||
js.sb.Append("]");
|
||||
}
|
||||
|
||||
static private void SerializeDictionary(JsonStringify js, object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
bool isFirst = true;
|
||||
js.sb.Append("{");
|
||||
|
||||
IDictionaryEnumerator id = (IDictionaryEnumerator)it;
|
||||
while (it.MoveNext())
|
||||
{
|
||||
if (isFirst)
|
||||
isFirst = false;
|
||||
else
|
||||
js.sb.Append(",");
|
||||
|
||||
object k = id.Key;
|
||||
if (Type.GetTypeCode(k.GetType()) != TypeCode.String)
|
||||
throw UnsupportedTypeException.DictionaryKey(k);
|
||||
|
||||
js.Stringify(k);
|
||||
|
||||
js.sb.Append(":");
|
||||
|
||||
object v = id.Value;
|
||||
js.Stringify(v);
|
||||
}
|
||||
|
||||
js.sb.Append("}");
|
||||
}
|
||||
|
||||
static private void SerializeIEnumerable(JsonStringify js, object obj)
|
||||
{
|
||||
IEnumerable ie = (IEnumerable)obj;
|
||||
IEnumerator it = ie.GetEnumerator();
|
||||
it.Reset();
|
||||
|
||||
if (it is IDictionaryEnumerator)
|
||||
SerializeDictionary(js, obj);
|
||||
else
|
||||
SerializeArray(js, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f777f23eee1ce44f8b5a9f635069c4d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,353 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace com.fpnn.common
|
||||
{
|
||||
/*
|
||||
* Close() is not necessary when using background thread.
|
||||
*
|
||||
* If using ErrorRecoder, please call TaskThreadPool.SetDefaultErrorRecorder()
|
||||
* before creating any instance, or call SetErrorRecorder() for each instance
|
||||
* before WakeUp()s are called.
|
||||
*/
|
||||
public class TaskThreadPool
|
||||
{
|
||||
public interface ITask
|
||||
{
|
||||
void Run();
|
||||
}
|
||||
|
||||
class ActionTask: ITask
|
||||
{
|
||||
Action action;
|
||||
|
||||
public ActionTask(Action act)
|
||||
{
|
||||
action = act;
|
||||
}
|
||||
public void Run()
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------[ fields ]-----------------------//
|
||||
private readonly int perfectCount;
|
||||
private readonly int maxCount;
|
||||
private readonly int maxQueueLength;
|
||||
|
||||
private static ErrorRecorder defaultErrorRecorder;
|
||||
|
||||
private class TaskThreadPoolCore
|
||||
{
|
||||
public bool backgroundThread;
|
||||
public int normalThreadCount;
|
||||
public int busyThreadCount;
|
||||
public int tempThreadCount;
|
||||
|
||||
public bool stopped;
|
||||
public Semaphore semaphore;
|
||||
public Semaphore quitSemaphore;
|
||||
public Queue<ITask> taskQueue;
|
||||
|
||||
public readonly int tempThreadLatencySeconds;
|
||||
public ErrorRecorder errorRecorder;
|
||||
|
||||
public TaskThreadPoolCore(int tempLatencySeconds, bool usingBackgroundThread)
|
||||
{
|
||||
backgroundThread = usingBackgroundThread;
|
||||
normalThreadCount = 0;
|
||||
busyThreadCount = 0;
|
||||
tempThreadCount = 0;
|
||||
|
||||
stopped = false;
|
||||
semaphore = new Semaphore(0, Int32.MaxValue);
|
||||
taskQueue = new Queue<ITask>();
|
||||
|
||||
tempThreadLatencySeconds = tempLatencySeconds;
|
||||
errorRecorder = defaultErrorRecorder;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly TaskThreadPoolCore core;
|
||||
|
||||
//----------------[ Constructor ]-----------------------//
|
||||
|
||||
public TaskThreadPool(int initThreadCount, int perfectThreadCount, int maxThreadCount, int maxQueueLengthLimitation = 0, int tempLatencySeconds = 60, bool usingBackGroundThread = true)
|
||||
{
|
||||
if (initThreadCount < 0)
|
||||
throw new ArgumentException("Param initThreadCount is less than Zero.", nameof(initThreadCount));
|
||||
|
||||
if (maxThreadCount <= 0)
|
||||
throw new ArgumentException("Param maxThreadCount is less than or equal to Zero.", nameof(maxThreadCount));
|
||||
|
||||
if (perfectThreadCount < initThreadCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(perfectThreadCount), "Param perfectThreadCount is less than initThreadCount");
|
||||
|
||||
if (maxThreadCount < perfectThreadCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxThreadCount), "Param maxThreadCount is less than perfectThreadCount");
|
||||
|
||||
perfectCount = perfectThreadCount;
|
||||
maxCount = maxThreadCount;
|
||||
maxQueueLength = maxQueueLengthLimitation;
|
||||
|
||||
core = new TaskThreadPoolCore(tempLatencySeconds, usingBackGroundThread);
|
||||
|
||||
for (int i = 0; i < initThreadCount; i++)
|
||||
{
|
||||
var thread = new Thread(Worker)
|
||||
{
|
||||
Name = "FPNN.ThreadPool.NormalWorker",
|
||||
IsBackground = core.backgroundThread
|
||||
};
|
||||
thread.Start(core);
|
||||
core.normalThreadCount++; //-- Unneed lock in there.
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetDefaultErrorRecorder(ErrorRecorder er)
|
||||
{
|
||||
defaultErrorRecorder = er;
|
||||
}
|
||||
|
||||
public void SetErrorRecorder(ErrorRecorder er)
|
||||
{
|
||||
core.errorRecorder = er;
|
||||
}
|
||||
|
||||
private void Append(bool normalThread)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread thread;
|
||||
if (normalThread)
|
||||
{
|
||||
thread = new Thread(Worker)
|
||||
{
|
||||
Name = "FPNN.ThreadPool.NormalWorker"
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
thread = new Thread(TempWorker)
|
||||
{
|
||||
Name = "FPNN.ThreadPool.TempWorker"
|
||||
};
|
||||
}
|
||||
|
||||
thread.IsBackground = core.backgroundThread;
|
||||
thread.Start(core);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
lock (core)
|
||||
{
|
||||
if (normalThread)
|
||||
core.normalThreadCount--;
|
||||
else
|
||||
core.tempThreadCount--;
|
||||
}
|
||||
core.errorRecorder?.RecordError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Wakeup(ITask task)
|
||||
{
|
||||
if (task == null)
|
||||
return false;
|
||||
|
||||
bool needAppend = false;
|
||||
bool appendNormalThread = false;
|
||||
lock (core)
|
||||
{
|
||||
if (core.stopped)
|
||||
return false;
|
||||
|
||||
if (maxQueueLength > 0 && core.taskQueue.Count >= maxQueueLength)
|
||||
return false;
|
||||
|
||||
core.taskQueue.Enqueue(task);
|
||||
|
||||
if (core.busyThreadCount + core.taskQueue.Count >= core.normalThreadCount + core.tempThreadCount)
|
||||
{
|
||||
if (core.normalThreadCount < perfectCount)
|
||||
{
|
||||
needAppend = true;
|
||||
appendNormalThread = true;
|
||||
core.normalThreadCount++;
|
||||
}
|
||||
else if (core.normalThreadCount + core.tempThreadCount < maxCount)
|
||||
{
|
||||
needAppend = true;
|
||||
appendNormalThread = false;
|
||||
core.tempThreadCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needAppend)
|
||||
{
|
||||
Append(appendNormalThread);
|
||||
}
|
||||
|
||||
core.semaphore.Release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Wakeup(Action action)
|
||||
{
|
||||
ActionTask task = new ActionTask(action);
|
||||
return Wakeup(task);
|
||||
}
|
||||
|
||||
//-- Please call this function in locked status.
|
||||
private static void ExitWorker(bool normal, TaskThreadPoolCore core)
|
||||
{
|
||||
if (normal)
|
||||
core.normalThreadCount--;
|
||||
else
|
||||
core.tempThreadCount--;
|
||||
|
||||
if (core.normalThreadCount == 0 && core.tempThreadCount == 0 && core.stopped && core.backgroundThread == false)
|
||||
core.quitSemaphore.Release();
|
||||
}
|
||||
|
||||
private static void Worker(Object obj)
|
||||
{
|
||||
TaskThreadPoolCore core = (TaskThreadPoolCore)obj;
|
||||
|
||||
while (true)
|
||||
{
|
||||
ITask task = null;
|
||||
core.semaphore.WaitOne();
|
||||
lock (core)
|
||||
{
|
||||
if (core.taskQueue.Count > 0)
|
||||
{
|
||||
task = core.taskQueue.Dequeue();
|
||||
core.busyThreadCount++;
|
||||
}
|
||||
else if (core.stopped)
|
||||
{
|
||||
ExitWorker(true, core);
|
||||
return;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
task.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (core.errorRecorder != null)
|
||||
core.errorRecorder.RecordError(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock(core)
|
||||
{
|
||||
core.busyThreadCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TempWorker(Object obj)
|
||||
{
|
||||
TaskThreadPoolCore core = (TaskThreadPoolCore)obj;
|
||||
int latencySeconds = core.tempThreadLatencySeconds;
|
||||
DateTime idleTime = DateTime.Now;
|
||||
|
||||
while (true)
|
||||
{
|
||||
ITask task = null;
|
||||
core.semaphore.WaitOne(latencySeconds * 1000);
|
||||
lock (core)
|
||||
{
|
||||
if (core.taskQueue.Count > 0)
|
||||
{
|
||||
task = core.taskQueue.Dequeue();
|
||||
core.busyThreadCount++;
|
||||
}
|
||||
else if (core.stopped)
|
||||
{
|
||||
ExitWorker(false, core);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
TimeSpan duration = DateTime.Now - idleTime;
|
||||
latencySeconds -= Convert.ToInt32(duration.TotalSeconds);
|
||||
|
||||
if (latencySeconds <= 0)
|
||||
{
|
||||
ExitWorker(false, core);
|
||||
return;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
task.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (core.errorRecorder != null)
|
||||
core.errorRecorder.RecordError(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (core)
|
||||
{
|
||||
core.busyThreadCount--;
|
||||
}
|
||||
}
|
||||
|
||||
idleTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
public void Close(bool dropAllTasks = false) //-- Synchronous method.
|
||||
{
|
||||
bool active = true;
|
||||
|
||||
lock (core)
|
||||
{
|
||||
if (core.stopped)
|
||||
return;
|
||||
|
||||
if (dropAllTasks)
|
||||
core.taskQueue.Clear();
|
||||
|
||||
if (core.backgroundThread || (core.normalThreadCount == 0 && core.tempThreadCount == 0))
|
||||
active = false;
|
||||
else
|
||||
core.quitSemaphore = new Semaphore(0, 1);
|
||||
|
||||
core.stopped = true;
|
||||
}
|
||||
|
||||
core.semaphore.Release(maxCount);
|
||||
|
||||
if (active)
|
||||
{
|
||||
core.quitSemaphore.WaitOne();
|
||||
core.quitSemaphore.Close();
|
||||
}
|
||||
|
||||
core.semaphore.Close();
|
||||
}
|
||||
|
||||
~TaskThreadPool()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66749982ee02845f4992ea52fe113376
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user