Proto2CS.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. namespace ET
  7. {
  8. internal class OpcodeInfo
  9. {
  10. public string Name;
  11. public int Opcode;
  12. }
  13. public static class Proto2CS
  14. {
  15. public static void Export()
  16. {
  17. InnerProto2CS.Proto2CS();
  18. }
  19. }
  20. public static partial class InnerProto2CS
  21. {
  22. private const string protoDir = "../Unity/Assets/Config/Proto";
  23. private const string clientMessagePath = "../Unity/Assets/Scripts/Model/Generate/Client/Message/";
  24. private const string serverMessagePath = "../Unity/Assets/Scripts/Model/Generate/Server/Message/";
  25. private const string clientServerMessagePath = "../Unity/Assets/Scripts/Model/Generate/ClientServer/Message/";
  26. private static readonly char[] splitChars = [' ', '\t'];
  27. private static readonly List<OpcodeInfo> msgOpcode = [];
  28. public static void Proto2CS()
  29. {
  30. msgOpcode.Clear();
  31. RemoveAllFilesExceptMeta(clientMessagePath);
  32. RemoveAllFilesExceptMeta(serverMessagePath);
  33. RemoveAllFilesExceptMeta(clientServerMessagePath);
  34. List<string> list = FileHelper.GetAllFiles(protoDir, "*proto");
  35. foreach (string s in list)
  36. {
  37. if (!s.EndsWith(".proto"))
  38. {
  39. continue;
  40. }
  41. string fileName = Path.GetFileNameWithoutExtension(s);
  42. string[] ss2 = fileName.Split('_');
  43. string protoName = ss2[0];
  44. string cs = ss2[1];
  45. int startOpcode = int.Parse(ss2[2]);
  46. ProtoFile2CS(fileName, protoName, cs, startOpcode);
  47. }
  48. RemoveUnusedMetaFiles(clientMessagePath);
  49. RemoveUnusedMetaFiles(serverMessagePath);
  50. RemoveUnusedMetaFiles(clientServerMessagePath);
  51. }
  52. private static void ProtoFile2CS(string fileName, string protoName, string cs, int startOpcode)
  53. {
  54. msgOpcode.Clear();
  55. string proto = Path.Combine(protoDir, $"{fileName}.proto");
  56. string s = File.ReadAllText(proto);
  57. StringBuilder sb = new();
  58. sb.Append("using MemoryPack;\n");
  59. sb.Append("using System.Collections.Generic;\n\n");
  60. sb.Append($"namespace ET\n");
  61. sb.Append("{\n");
  62. bool isMsgStart = false;
  63. string msgName = "";
  64. string responseType = "";
  65. StringBuilder sbDispose = new();
  66. Regex responseTypeRegex = ResponseTypeRegex();
  67. foreach (string line in s.Split('\n'))
  68. {
  69. string newline = line.Trim();
  70. if (string.IsNullOrEmpty(newline))
  71. {
  72. continue;
  73. }
  74. if (responseTypeRegex.IsMatch(newline))
  75. {
  76. responseType = responseTypeRegex.Replace(newline, string.Empty);
  77. responseType = responseType.Trim().Split(' ')[0].TrimEnd('\r', '\n');
  78. continue;
  79. }
  80. if (!isMsgStart && newline.StartsWith("//"))
  81. {
  82. if (newline.StartsWith("///"))
  83. {
  84. sb.Append("\t/// <summary>\n");
  85. sb.Append($"\t/// {newline.TrimStart('/', ' ')}\n");
  86. sb.Append("\t/// </summary>\n");
  87. }
  88. else
  89. {
  90. sb.Append($"\t// {newline.TrimStart('/', ' ')}\n");
  91. }
  92. continue;
  93. }
  94. if (newline.StartsWith("message"))
  95. {
  96. isMsgStart = true;
  97. string parentClass = "";
  98. msgName = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries)[1];
  99. string[] ss = newline.Split(["//"], StringSplitOptions.RemoveEmptyEntries);
  100. if (ss.Length == 2)
  101. {
  102. parentClass = ss[1].Trim();
  103. }
  104. msgOpcode.Add(new OpcodeInfo() { Name = msgName, Opcode = ++startOpcode });
  105. sb.Append($"\t[MemoryPackable]\n");
  106. sb.Append($"\t[Message({protoName}.{msgName})]\n");
  107. if (!string.IsNullOrEmpty(responseType))
  108. {
  109. sb.Append($"\t[ResponseType(nameof({responseType}))]\n");
  110. }
  111. sb.Append($"\tpublic partial class {msgName} : MessageObject");
  112. if (parentClass is "IActorMessage" or "IActorRequest" or "IActorResponse")
  113. {
  114. sb.Append($", {parentClass}\n");
  115. }
  116. else if (parentClass != "")
  117. {
  118. sb.Append($", {parentClass}\n");
  119. }
  120. else
  121. {
  122. sb.Append('\n');
  123. }
  124. continue;
  125. }
  126. if (isMsgStart)
  127. {
  128. if (newline.StartsWith('{'))
  129. {
  130. sbDispose.Clear();
  131. sb.Append("\t{\n");
  132. sb.Append($"\t\tpublic static {msgName} Create(bool isFromPool = false)\n\t\t{{\n\t\t\treturn ObjectPool.Fetch<{msgName}>(isFromPool);\n\t\t}}\n\n");
  133. continue;
  134. }
  135. if (newline.StartsWith('}'))
  136. {
  137. isMsgStart = false;
  138. responseType = "";
  139. // 加了no dispose则自己去定义dispose函数,不要自动生成
  140. if (!newline.Contains("// no dispose"))
  141. {
  142. sb.Append($"\t\tpublic override void Dispose()\n\t\t{{\n\t\t\tif (!this.IsFromPool)\n\t\t\t{{\n\t\t\t\treturn;\n\t\t\t}}\n\n\t\t\t{sbDispose.ToString().TrimEnd('\t')}\n\t\t\tObjectPool.Recycle(this);\n\t\t}}\n");
  143. }
  144. sb.Append("\t}\n\n");
  145. continue;
  146. }
  147. if (newline.StartsWith("//"))
  148. {
  149. sb.Append("\t\t/// <summary>\n");
  150. sb.Append($"\t\t/// {newline.TrimStart('/', ' ')}\n");
  151. sb.Append("\t\t/// </summary>\n");
  152. continue;
  153. }
  154. string memberStr;
  155. if (newline.Contains("//"))
  156. {
  157. string[] lineSplit = newline.Split("//");
  158. memberStr = lineSplit[0].Trim();
  159. sb.Append("\t\t/// <summary>\n");
  160. sb.Append($"\t\t/// {lineSplit[1].Trim()}\n");
  161. sb.Append("\t\t/// </summary>\n");
  162. }
  163. else
  164. {
  165. memberStr = newline;
  166. }
  167. if (memberStr.StartsWith("map<"))
  168. {
  169. Map(sb, memberStr, sbDispose);
  170. }
  171. else if (memberStr.StartsWith("repeated"))
  172. {
  173. Repeated(sb, memberStr, sbDispose);
  174. }
  175. else
  176. {
  177. Members(sb, memberStr, sbDispose);
  178. }
  179. }
  180. }
  181. sb.Append("\tpublic static class " + protoName + "\n\t{\n");
  182. foreach (OpcodeInfo info in msgOpcode)
  183. {
  184. sb.Append($"\t\tpublic const ushort {info.Name} = {info.Opcode};\n");
  185. }
  186. sb.Append("\t}\n");
  187. sb.Append('}');
  188. sb.Replace("\t", " ");
  189. string result = sb.ToString().ReplaceLineEndings("\r\n");
  190. if (cs.Contains('C'))
  191. {
  192. GenerateCS(result, clientMessagePath, proto);
  193. GenerateCS(result, serverMessagePath, proto);
  194. GenerateCS(result, clientServerMessagePath, proto);
  195. }
  196. if (cs.Contains('S'))
  197. {
  198. GenerateCS(result, serverMessagePath, proto);
  199. GenerateCS(result, clientServerMessagePath, proto);
  200. }
  201. }
  202. private static void GenerateCS(string result, string path, string proto)
  203. {
  204. if (!Directory.Exists(path))
  205. {
  206. Directory.CreateDirectory(path);
  207. }
  208. string csPath = Path.Combine(path, Path.GetFileNameWithoutExtension(proto) + ".cs");
  209. using FileStream txt = new(csPath, FileMode.Create, FileAccess.ReadWrite);
  210. using StreamWriter sw = new(txt);
  211. sw.Write(result);
  212. }
  213. private static void Map(StringBuilder sb, string newline, StringBuilder sbDispose)
  214. {
  215. int start = newline.IndexOf('<') + 1;
  216. int end = newline.IndexOf('>');
  217. string types = newline.Substring(start, end - start);
  218. string[] ss = types.Split(',');
  219. string keyType = ConvertType(ss[0].Trim());
  220. string valueType = ConvertType(ss[1].Trim());
  221. string tail = newline[(end + 1)..];
  222. ss = tail.Trim().Replace(";", "").Split(' ');
  223. string v = ss[0];
  224. int n = int.Parse(ss[2]);
  225. sb.Append("\t\t[MongoDB.Bson.Serialization.Attributes.BsonDictionaryOptions(MongoDB.Bson.Serialization.Options.DictionaryRepresentation.ArrayOfArrays)]\n");
  226. sb.Append($"\t\t[MemoryPackOrder({n - 1})]\n");
  227. sb.Append($"\t\tpublic Dictionary<{keyType}, {valueType}> {v} {{ get; set; }} = new();\n");
  228. sbDispose.Append($"this.{v}.Clear();\n\t\t\t");
  229. }
  230. private static void Repeated(StringBuilder sb, string newline, StringBuilder sbDispose)
  231. {
  232. try
  233. {
  234. int index = newline.IndexOf(';');
  235. newline = newline.Remove(index);
  236. string[] ss = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);
  237. string type = ss[1];
  238. type = ConvertType(type);
  239. string name = ss[2];
  240. int n = int.Parse(ss[4]);
  241. sb.Append($"\t\t[MemoryPackOrder({n - 1})]\n");
  242. sb.Append($"\t\tpublic List<{type}> {name} {{ get; set; }} = new();\n\n");
  243. sbDispose.Append($"this.{name}.Clear();\n\t\t\t");
  244. }
  245. catch (Exception e)
  246. {
  247. Console.WriteLine($"{newline}\n {e}");
  248. }
  249. }
  250. private static string ConvertType(string type)
  251. {
  252. return type switch
  253. {
  254. "int16" => "short",
  255. "int32" => "int",
  256. "bytes" => "byte[]",
  257. "uint32" => "uint",
  258. "long" => "long",
  259. "int64" => "long",
  260. "uint64" => "ulong",
  261. "uint16" => "ushort",
  262. _ => type
  263. };
  264. }
  265. private static void Members(StringBuilder sb, string newline, StringBuilder sbDispose)
  266. {
  267. try
  268. {
  269. int index = newline.IndexOf(';');
  270. newline = newline.Remove(index);
  271. string[] ss = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);
  272. string type = ss[0];
  273. string name = ss[1];
  274. int n = int.Parse(ss[3]);
  275. string typeCs = ConvertType(type);
  276. sb.Append($"\t\t[MemoryPackOrder({n - 1})]\n");
  277. sb.Append($"\t\tpublic {typeCs} {name} {{ get; set; }}\n\n");
  278. switch (typeCs)
  279. {
  280. case "bytes":
  281. {
  282. break;
  283. }
  284. default:
  285. sbDispose.Append($"this.{name} = default;\n\t\t\t");
  286. break;
  287. }
  288. }
  289. catch (Exception e)
  290. {
  291. Console.WriteLine($"{newline}\n {e}");
  292. }
  293. }
  294. /// <summary>
  295. /// 删除meta以外的所有文件
  296. /// </summary>
  297. static void RemoveAllFilesExceptMeta(string directory)
  298. {
  299. if (!Directory.Exists(directory))
  300. {
  301. return;
  302. }
  303. DirectoryInfo targetDir = new(directory);
  304. FileInfo[] fileInfos = targetDir.GetFiles("*", SearchOption.AllDirectories);
  305. foreach (FileInfo info in fileInfos)
  306. {
  307. if (!info.Name.EndsWith(".meta"))
  308. {
  309. File.Delete(info.FullName);
  310. }
  311. }
  312. }
  313. /// <summary>
  314. /// 删除多余的meta文件
  315. /// </summary>
  316. static void RemoveUnusedMetaFiles(string directory)
  317. {
  318. if (!Directory.Exists(directory))
  319. {
  320. return;
  321. }
  322. DirectoryInfo targetDir = new(directory);
  323. FileInfo[] fileInfos = targetDir.GetFiles("*.meta", SearchOption.AllDirectories);
  324. foreach (FileInfo info in fileInfos)
  325. {
  326. string pathWithoutMeta = info.FullName.Remove(info.FullName.LastIndexOf(".meta", StringComparison.Ordinal));
  327. if (!File.Exists(pathWithoutMeta) && !Directory.Exists(pathWithoutMeta))
  328. {
  329. File.Delete(info.FullName);
  330. }
  331. }
  332. }
  333. [GeneratedRegex(@"//\s*ResponseType")]
  334. private static partial Regex ResponseTypeRegex();
  335. }
  336. }