diff --git a/src/LubanAssistant/AssistantTab.Designer.cs b/src/LubanAssistant/AssistantTab.Designer.cs
new file mode 100644
index 0000000..9f5542b
--- /dev/null
+++ b/src/LubanAssistant/AssistantTab.Designer.cs
@@ -0,0 +1,152 @@
+
+namespace LubanAssistant
+{
+ partial class AssistantTab : Microsoft.Office.Tools.Ribbon.RibbonBase
+ {
+ ///
+ /// 必需的设计器变量。
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ public AssistantTab()
+ : base(Globals.Factory.GetRibbonFactory())
+ {
+ InitializeComponent();
+ }
+
+ ///
+ /// 清理所有正在使用的资源。
+ ///
+ /// 如果应释放托管资源,为 true;否则为 false。
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region 组件设计器生成的代码
+
+ ///
+ /// 设计器支持所需的方法 - 不要修改
+ /// 使用代码编辑器修改此方法的内容。
+ ///
+ private void InitializeComponent()
+ {
+ this.tab1 = this.Factory.CreateRibbonTab();
+ this.group3 = this.Factory.CreateRibbonGroup();
+ this.SetRootFile = this.Factory.CreateRibbonButton();
+ this.group1 = this.Factory.CreateRibbonGroup();
+ this.load = this.Factory.CreateRibbonButton();
+ this.group2 = this.Factory.CreateRibbonGroup();
+ this.saveAll = this.Factory.CreateRibbonButton();
+ this.saveSelected = this.Factory.CreateRibbonButton();
+ this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
+ this.tab1.SuspendLayout();
+ this.group3.SuspendLayout();
+ this.group1.SuspendLayout();
+ this.group2.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // tab1
+ //
+ this.tab1.ControlId.ControlIdType = Microsoft.Office.Tools.Ribbon.RibbonControlIdType.Office;
+ this.tab1.Groups.Add(this.group3);
+ this.tab1.Groups.Add(this.group1);
+ this.tab1.Groups.Add(this.group2);
+ this.tab1.Label = "TabAddIns";
+ this.tab1.Name = "tab1";
+ //
+ // group3
+ //
+ this.group3.Items.Add(this.SetRootFile);
+ this.group3.Name = "group3";
+ //
+ // SetRootFile
+ //
+ this.SetRootFile.ControlSize = Microsoft.Office.Core.RibbonControlSize.RibbonControlSizeLarge;
+ this.SetRootFile.Label = "设置Root文件";
+ this.SetRootFile.Name = "SetRootFile";
+ this.SetRootFile.ShowImage = true;
+ this.SetRootFile.Click += new Microsoft.Office.Tools.Ribbon.RibbonControlEventHandler(this.BtnChooseRootFileClick);
+ //
+ // group1
+ //
+ this.group1.Items.Add(this.load);
+ this.group1.Name = "group1";
+ //
+ // load
+ //
+ this.load.ControlSize = Microsoft.Office.Core.RibbonControlSize.RibbonControlSizeLarge;
+ this.load.Label = "加载数据表";
+ this.load.Name = "load";
+ this.load.ShowImage = true;
+ this.load.Click += new Microsoft.Office.Tools.Ribbon.RibbonControlEventHandler(this.BtnLoadClick);
+ //
+ // group2
+ //
+ this.group2.Items.Add(this.saveAll);
+ this.group2.Items.Add(this.saveSelected);
+ this.group2.Name = "group2";
+ //
+ // saveAll
+ //
+ this.saveAll.ControlSize = Microsoft.Office.Core.RibbonControlSize.RibbonControlSizeLarge;
+ this.saveAll.Label = "保存所有";
+ this.saveAll.Name = "saveAll";
+ this.saveAll.ShowImage = true;
+ this.saveAll.Click += new Microsoft.Office.Tools.Ribbon.RibbonControlEventHandler(this.BtnSaveAllClick);
+ //
+ // saveSelected
+ //
+ this.saveSelected.ControlSize = Microsoft.Office.Core.RibbonControlSize.RibbonControlSizeLarge;
+ this.saveSelected.Label = "保存选中";
+ this.saveSelected.Name = "saveSelected";
+ this.saveSelected.ShowImage = true;
+ this.saveSelected.Click += new Microsoft.Office.Tools.Ribbon.RibbonControlEventHandler(this.BtnSaveSelectedClick);
+ //
+ // openFileDialog1
+ //
+ this.openFileDialog1.FileName = "openFileDialog1";
+ //
+ // AssistantTab
+ //
+ this.Name = "AssistantTab";
+ this.RibbonType = "Microsoft.Excel.Workbook";
+ this.Tabs.Add(this.tab1);
+ this.Load += new Microsoft.Office.Tools.Ribbon.RibbonUIEventHandler(this.AssistantTab_Load);
+ this.tab1.ResumeLayout(false);
+ this.tab1.PerformLayout();
+ this.group3.ResumeLayout(false);
+ this.group3.PerformLayout();
+ this.group1.ResumeLayout(false);
+ this.group1.PerformLayout();
+ this.group2.ResumeLayout(false);
+ this.group2.PerformLayout();
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ internal Microsoft.Office.Tools.Ribbon.RibbonTab tab1;
+ internal Microsoft.Office.Tools.Ribbon.RibbonGroup group1;
+ internal Microsoft.Office.Tools.Ribbon.RibbonButton load;
+ internal Microsoft.Office.Tools.Ribbon.RibbonGroup group2;
+ internal Microsoft.Office.Tools.Ribbon.RibbonButton saveAll;
+ internal Microsoft.Office.Tools.Ribbon.RibbonGroup group3;
+ internal Microsoft.Office.Tools.Ribbon.RibbonButton SetRootFile;
+ internal Microsoft.Office.Tools.Ribbon.RibbonButton saveSelected;
+ private System.Windows.Forms.OpenFileDialog openFileDialog1;
+ }
+
+ partial class ThisRibbonCollection
+ {
+ internal AssistantTab AssistantTab
+ {
+ get { return this.GetRibbon(); }
+ }
+ }
+}
diff --git a/src/LubanAssistant/AssistantTab.cs b/src/LubanAssistant/AssistantTab.cs
new file mode 100644
index 0000000..74b00f8
--- /dev/null
+++ b/src/LubanAssistant/AssistantTab.cs
@@ -0,0 +1,82 @@
+using Microsoft.Office.Tools.Ribbon;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+
+namespace LubanAssistant
+{
+ public partial class AssistantTab
+ {
+ private string RootDefineFile
+ {
+ get => Properties.Settings.Default.rootDefineFile;
+ set
+ {
+ Properties.Settings.Default.rootDefineFile = value;
+ Properties.Settings.Default.Save();
+ }
+ }
+
+ private void AssistantTab_Load(object sender, RibbonUIEventArgs e)
+ {
+ }
+
+ private bool CheckChooseRootDefineFile()
+ {
+ if (string.IsNullOrWhiteSpace(RootDefineFile) || !File.Exists(RootDefineFile))
+ {
+ if (TryChooseRootDefineFile(out var rootDefineFile))
+ {
+ RootDefineFile = rootDefineFile;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private bool TryChooseRootDefineFile(out string rootDefineFile)
+ {
+ var dialog = new OpenFileDialog();
+ dialog.DefaultExt = "xml";
+ dialog.Filter = "root file (*.xml)|*.xml";
+ dialog.Title = "Choose Root Xml File";
+ dialog.CheckFileExists = true;
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ rootDefineFile = dialog.FileName;
+ return true;
+ }
+ rootDefineFile = null;
+ return false;
+ }
+
+ private void BtnChooseRootFileClick(object sender, RibbonControlEventArgs e)
+ {
+ if (TryChooseRootDefineFile(out var rootDefineFile))
+ {
+ RootDefineFile = rootDefineFile;
+ }
+ }
+
+ private void BtnLoadClick(object sender, RibbonControlEventArgs e)
+ {
+ if (CheckChooseRootDefineFile())
+ {
+ MessageBox.Show("load");
+ }
+ }
+
+ private void BtnSaveAllClick(object sender, RibbonControlEventArgs e)
+ {
+ MessageBox.Show("点击save");
+ }
+
+ private void BtnSaveSelectedClick(object sender, RibbonControlEventArgs e)
+ {
+ MessageBox.Show("点击save");
+ }
+ }
+}
diff --git a/src/LubanAssistant/AssistantTab.resx b/src/LubanAssistant/AssistantTab.resx
new file mode 100644
index 0000000..9bad2f5
--- /dev/null
+++ b/src/LubanAssistant/AssistantTab.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
\ No newline at end of file
diff --git a/src/LubanAssistant/LubanAssistant.csproj b/src/LubanAssistant/LubanAssistant.csproj
index 9702613..0d37324 100644
--- a/src/LubanAssistant/LubanAssistant.csproj
+++ b/src/LubanAssistant/LubanAssistant.csproj
@@ -1,4 +1,5 @@
+
Excel
+
+ 9.0
+
+
+ ..\packages\CommandLineParser.2.8.0\lib\net461\CommandLine.dll
+
+
+ ..\packages\ExcelDataReader.3.6.0\lib\net45\ExcelDataReader.dll
+
+
+ ..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll
+
+
+ ..\packages\NeoLua.1.3.13\lib\net47\Neo.Lua.dll
+
+
+ ..\packages\NLog.4.7.11\lib\net45\NLog.dll
+
+
+ ..\packages\Scriban.4.1.0\lib\netstandard2.0\Scriban.dll
+
+
+ ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll
+
+
+
+
+ ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll
+
+
+
+ ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll
+
+
+ ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll
+
+
+
+
+ ..\packages\System.Text.Encodings.Web.5.0.1\lib\net461\System.Text.Encodings.Web.dll
+
+
+ ..\packages\System.Text.Json.5.0.2\lib\net461\System.Text.Json.dll
+
+
+ ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll
+
+
+
+ ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll
+
+
+ ..\packages\YamlDotNet.11.2.1\lib\net45\YamlDotNet.dll
+
@@ -156,9 +214,309 @@
can be found.
-->
+
+ Source\EErrorCode.cs
+
+
+ Source\Utils\FileUtil.cs
+
+
+ Source\Utils\LogUtil.cs
+
+
+ Source\Utils\TypeUtil.cs
+
+
+ Source\Utils\XmlUtil.cs
+
+
+ Source\DataCreators\DataCreateException.cs
+
+
+ Source\DataCreators\ExcelDataCreator.cs
+
+
+ Source\DataCreators\ExcelNamedRowDataCreator.cs
+
+
+ Source\DataCreators\JsonDataCreator.cs
+
+
+ Source\DataCreators\MultiRowExcelDataCreator.cs
+
+
+ Source\DataCreators\StringDataCreator.cs
+
+
+ Source\DataSources\AbstractDataSource.cs
+
+
+ Source\DataSources\Excel\ExcelDataSource.cs
+
+
+ Source\DataSources\Excel\ExcelStream.cs
+
+
+ Source\DataSources\Excel\Sheet.cs
+
+
+ Source\DataSources\Json\JsonDataSource.cs
+
+
+ Source\Datas\DArray.cs
+
+
+ Source\Datas\DBean.cs
+
+
+ Source\Datas\DBool.cs
+
+
+ Source\Datas\DByte.cs
+
+
+ Source\Datas\DBytes.cs
+
+
+ Source\Datas\DDateTime.cs
+
+
+ Source\Datas\DDouble.cs
+
+
+ Source\Datas\DEnum.cs
+
+
+ Source\Datas\DFint.cs
+
+
+ Source\Datas\DFloat.cs
+
+
+ Source\Datas\DFlong.cs
+
+
+ Source\Datas\DFshort.cs
+
+
+ Source\Datas\DInt.cs
+
+
+ Source\Datas\DList.cs
+
+
+ Source\Datas\DLong.cs
+
+
+ Source\Datas\DMap.cs
+
+
+ Source\Datas\DSet.cs
+
+
+ Source\Datas\DShort.cs
+
+
+ Source\Datas\DString.cs
+
+
+ Source\Datas\DVector2.cs
+
+
+ Source\Datas\DVector3.cs
+
+
+ Source\Datas\DVector4.cs
+
+
+ Source\Datas\Record.cs
+
+
+ Source\DataVisitors\IDataActionVisitor.cs
+
+
+ Source\DataVisitors\IDataFuncVisitor.cs
+
+
+ Source\Defs\CfgDefTypeBase.cs
+
+
+ Source\Defs\DefBean.cs
+
+
+ Source\Defs\DefTable.cs
+
+
+ Source\RawDefs\CfgBean.cs
+
+
+ Source\RawDefs\CfgField.cs
+
+
+ Source\RawDefs\Defines.cs
+
+
+ Source\RawDefs\Group.cs
+
+
+ Source\RawDefs\Patch.cs
+
+
+ Source\RawDefs\ResourceInfo.cs
+
+
+ Source\RawDefs\Service.cs
+
+
+ Source\RawDefs\Table.cs
+
+
+ Source\TypeVisitors\DeepCompareTypeDefine.cs
+
+
+ Source\TypeVisitors\IsMultiData.cs
+
+
+ Source\TypeVisitors\IsNotSepTypeVisitor.cs
+
+
+ Source\Defs\RefTypeVisitor.cs
+
+
+ Source\Utils\DataUtil.cs
+
+
+ Source\Defs\CommonDefLoader.cs
+
+
+ Source\Defs\DefAssemblyBase.cs
+
+
+ Source\Defs\DefBeanBase.cs
+
+
+ Source\Defs\DefEnum.cs
+
+
+ Source\Defs\DefFieldBase.cs
+
+
+ Source\ELanguage.cs
+
+
+ Source\RawDefs\Bean.cs
+
+
+ Source\RawDefs\Field.cs
+
+
+ Source\RawDefs\PEnum.cs
+
+
+ Source\Types\TArray.cs
+
+
+ Source\Types\TBean.cs
+
+
+ Source\Types\TBool.cs
+
+
+ Source\Types\TByte.cs
+
+
+ Source\Types\TBytes.cs
+
+
+ Source\Types\TDateTime.cs
+
+
+ Source\Types\TDouble.cs
+
+
+ Source\Types\TEnum.cs
+
+
+ Source\Types\TFint.cs
+
+
+ Source\Types\TFloat.cs
+
+
+ Source\Types\TFlong.cs
+
+
+ Source\Types\TFshort.cs
+
+
+ Source\Types\TInt.cs
+
+
+ Source\Types\TList.cs
+
+
+ Source\Types\TLong.cs
+
+
+ Source\Types\TMap.cs
+
+
+ Source\Types\TSet.cs
+
+
+ Source\Types\TShort.cs
+
+
+ Source\Types\TString.cs
+
+
+ Source\Types\TText.cs
+
+
+ Source\Types\TType.cs
+
+
+ Source\Types\TVector2.cs
+
+
+ Source\Types\TVector3.cs
+
+
+ Source\Types\TVector4.cs
+
+
+ Source\TypeVisitors\AllFalseVisitor.cs
+
+
+ Source\TypeVisitors\AllTrueVisitor.cs
+
+
+ Source\TypeVisitors\DecoratorFuncVisitor.cs
+
+
+ Source\TypeVisitors\ITypeActionVisitor.cs
+
+
+ Source\TypeVisitors\ITypeFuncVisitor.cs
+
+
+ Source\Utils\DefUtil.cs
+
+
+ Source\IAgent.cs
+
+
+ Component
+
+
+ AssistantTab.cs
+
Code
+
+ AssistantTab.cs
+
ResXFileCodeGenerator
Resources.Designer.cs
@@ -168,6 +526,9 @@
True
Resources.resx
+
+
+
SettingsSingleFileGenerator
Settings.Designer.cs
@@ -175,7 +536,37 @@
True
Settings.settings
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Code
@@ -187,10 +578,22 @@
+
+
+
10.0
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+ true
+
+
+ LubanAssistant_TemporaryKey.pfx
+
+
+ DB8575295993B72B55091F842E5704CA3A263CD5
+
@@ -206,4 +609,12 @@
+
+
+ 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。
+
+
+
+
+
\ No newline at end of file
diff --git a/src/LubanAssistant/Properties/Settings.Designer.cs b/src/LubanAssistant/Properties/Settings.Designer.cs
index de65ca4..fb719cf 100644
--- a/src/LubanAssistant/Properties/Settings.Designer.cs
+++ b/src/LubanAssistant/Properties/Settings.Designer.cs
@@ -1,10 +1,10 @@
//------------------------------------------------------------------------------
//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
+// 此代码由工具生成。
+// 运行时版本:4.0.30319.42000
//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
+// 对此文件的更改可能会导致不正确的行为,并且如果
+// 重新生成代码,这些更改将会丢失。
//
//------------------------------------------------------------------------------
@@ -12,7 +12,7 @@ namespace LubanAssistant.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -22,5 +22,17 @@ namespace LubanAssistant.Properties {
return defaultInstance;
}
}
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string rootDefineFile {
+ get {
+ return ((string)(this["rootDefineFile"]));
+ }
+ set {
+ this["rootDefineFile"] = value;
+ }
+ }
}
}
diff --git a/src/LubanAssistant/Properties/Settings.settings b/src/LubanAssistant/Properties/Settings.settings
index 3964565..c3d99c0 100644
--- a/src/LubanAssistant/Properties/Settings.settings
+++ b/src/LubanAssistant/Properties/Settings.settings
@@ -1,7 +1,9 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/LubanAssistant/Source/AtomicLong.cs b/src/LubanAssistant/Source/AtomicLong.cs
new file mode 100644
index 0000000..e4c77ad
--- /dev/null
+++ b/src/LubanAssistant/Source/AtomicLong.cs
@@ -0,0 +1,36 @@
+using System.Threading;
+
+namespace Bright.Threading
+{
+ public class AtomicLong
+ {
+ private long _value;
+ public AtomicLong(long initValue = 0)
+ {
+ _value = initValue;
+ }
+
+ public long IncrementAndGet()
+ {
+ return Interlocked.Add(ref _value, 1);
+ }
+
+ public long AddAndGet(long step)
+ {
+ return Interlocked.Add(ref _value, step);
+ }
+
+ public long GetAndAdd(long step)
+ {
+ return Interlocked.Add(ref _value, step);
+ }
+
+ public long Value { get => Interlocked.Read(ref _value); set => Interlocked.Exchange(ref _value, value); }
+
+
+ public override string ToString()
+ {
+ return _value.ToString();
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/CacheFileUtil.cs b/src/LubanAssistant/Source/CacheFileUtil.cs
new file mode 100644
index 0000000..7a338ba
--- /dev/null
+++ b/src/LubanAssistant/Source/CacheFileUtil.cs
@@ -0,0 +1,56 @@
+using Luban.Common.Protos;
+using Luban.Common.Utils;
+using Luban.Server.Common;
+using System;
+using System.Threading.Tasks;
+
+namespace Luban.Job.Common.Utils
+{
+ public static class CacheFileUtil
+ {
+ private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
+
+ public static string GenStringOrBytesMd5AndAddCache(string fileName, object content)
+ {
+ switch (content)
+ {
+ case string s: return GenMd5AndAddCache(fileName, s);
+ case byte[] bs: return GenMd5AndAddCache(fileName, bs);
+ default: throw new System.NotSupportedException();
+ }
+ }
+
+ public static string GenMd5AndAddCache(string fileName, string content, bool withUtf8Bom = false)
+ {
+ content = content.Replace("\r\n", "\n");
+ byte[] bytes;
+ if (!withUtf8Bom)
+ {
+ bytes = System.Text.Encoding.UTF8.GetBytes(content);
+ }
+ else
+ {
+ //bytes = new byte[System.Text.Encoding.UTF8.GetByteCount(content) + 3 /* bom header */];
+ //bytes[0] = 0xef;
+ //bytes[1] = 0xbb;
+ //bytes[2] = 0xbf;
+ //System.Text.Encoding.UTF8.GetBytes(content, bytes.AsSpan(3));
+ throw new System.NotSupportedException();
+ }
+ var md5 = FileUtil.CalcMD5(bytes);
+#if !LUBAN_ASSISTANT
+ CacheManager.Ins.AddCache(fileName, md5, bytes);
+#endif
+ return md5;
+ }
+
+ public static string GenMd5AndAddCache(string fileName, byte[] bytes)
+ {
+ var md5 = FileUtil.CalcMD5(bytes);
+#if !LUBAN_ASSISTANT
+ CacheManager.Ins.AddCache(fileName, md5, bytes);
+#endif
+ return md5;
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Collections/CollectionExtension.cs b/src/LubanAssistant/Source/Collections/CollectionExtension.cs
new file mode 100644
index 0000000..2df013e
--- /dev/null
+++ b/src/LubanAssistant/Source/Collections/CollectionExtension.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Collections.Generic;
+
+namespace Bright.Collections
+{
+ public static class CollectionExtension
+ {
+
+ public static void AddRange(this IList dst, IEnumerable src)
+ {
+ foreach (var x in src)
+ {
+ dst.Add(x);
+ }
+ }
+
+ public static bool TryAdd(this IDictionary map, TK key, TV value)
+ {
+ if (map.ContainsKey(key))
+ {
+ return false;
+ }
+ map.Add(key, value);
+ return true;
+ }
+
+ public static TV GetValueOrDefault(this IDictionary map, TK key, TV defaultValue = default)
+ {
+ return map.TryGetValue(key, out var value) ? value : defaultValue;
+ }
+
+ public static TV GetOrAdd(this IDictionary map, TK key, Func creator)
+ {
+ if (map.TryGetValue(key, out var value))
+ {
+ return value;
+ }
+ else
+ {
+ TV newValue = creator(key);
+ map.Add(key, newValue);
+ return newValue;
+ }
+ }
+
+ public static TV GetOrAdd(this IDictionary map, TK key) where TV : new()
+ {
+ if (map.TryGetValue(key, out var value))
+ {
+ return value;
+ }
+ else
+ {
+ TV newValue = new TV();
+ map.Add(key, newValue);
+ return newValue;
+ }
+ }
+
+ public static TV Get(this IDictionary map, TK key) where TV : class
+ {
+ return map.TryGetValue(key, out var value) ? value : null;
+ }
+
+ public static bool Contains(this IDictionary map, TK key, TV value)
+ {
+ return map.Contains(new KeyValuePair(key, value));
+ }
+
+ public static void PutAll(this IDictionary dst, IDictionary src)
+ {
+ foreach (var e in src)
+ {
+ dst[e.Key] = e.Value;
+ }
+ }
+
+ public static bool IsEmpty(this ICollection c)
+ {
+ return c.Count == 0;
+ }
+
+ public static void Merge(this IDictionary dst, IDictionary src)
+ {
+ foreach (var e in src)
+ {
+ if (dst.TryGetValue(e.Key, out var v))
+ {
+ dst[e.Key] = v + e.Value;
+ }
+ else
+ {
+ dst[e.Key] = e.Value;
+ }
+ }
+ }
+
+ public static void Replace(this IDictionary dst, IDictionary src)
+ {
+ dst.Clear();
+ dst.PutAll(src);
+ }
+
+ public static int AddValue(this IDictionary dst, T key, int add)
+ {
+ if (dst.TryGetValue(key, out var value))
+ {
+ return dst[key] = value + add;
+ }
+ else
+ {
+ return dst[key] = add;
+ }
+ }
+
+ public static long AddValue(this IDictionary dst, T key, long add)
+ {
+ if (dst.TryGetValue(key, out var value))
+ {
+ return dst[key] = value + add;
+ }
+ else
+ {
+ return dst[key] = add;
+ }
+ }
+
+ public static int IncrementValue(this IDictionary dst, T key)
+ {
+ if (dst.TryGetValue(key, out var value))
+ {
+ return dst[key] = value + 1;
+ }
+ else
+ {
+ return dst[key] = 1;
+ }
+ }
+
+ public static long IncrementValue(this IDictionary dst, T key)
+ {
+ if (dst.TryGetValue(key, out var value))
+ {
+ return dst[key] = value + 1;
+ }
+ else
+ {
+ return dst[key] = 1;
+ }
+ }
+
+ public static bool ContainsAll(this IList a, IList b)
+ {
+ foreach (var x in b)
+ {
+ if (!a.Contains(x))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ public static void RemoveAll(this IList a, IList b)
+ {
+ foreach (var x in b)
+ {
+ a.Remove(x);
+ }
+ }
+
+ public static bool DicEquals(this IDictionary a, IDictionary b)
+ {
+ if (a.Count != b.Count)
+ {
+ return false;
+ }
+ foreach (var e in a)
+ {
+ if (!(b.TryGetValue(e.Key, out var v) && v.Equals(e.Value)))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static void AddAll(this Dictionary data, Dictionary b)
+ {
+ foreach (var e in b)
+ {
+ data[e.Key] = e.Value;
+ }
+ }
+
+ public static Dictionary Plus(this Dictionary data, TK key, TV value)
+ {
+ var newMap = new Dictionary(data)
+ {
+ [key] = value
+ };
+ return newMap;
+ }
+
+
+
+ public static T[] CopySubArray(this T[] data, int index, int length)
+ {
+ T[] result = new T[length];
+ Array.Copy(data, index, result, 0, length);
+ return result;
+ }
+
+ ///
+ /// 返回第一个满足条件的元素的index
+ ///
+ ///
+ ///
+ ///
+ /// 返回第一个满足条件的元素的index,如果没找到,返回 -1
+ public static int IndexOfFirst(IEnumerable arr, Func predic)
+ {
+ int i = 0;
+ foreach (var x in arr)
+ {
+ if (predic(x))
+ {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ ///
+ /// 返回最后一个满足条件的元素的index
+ ///
+ ///
+ ///
+ ///
+ /// 返回最后一个满足条件的元素的index,如果没找到,返回 -1
+ public static int IndexOfLast(IEnumerable arr, Func predic)
+ {
+ int i = -1;
+ foreach (var x in arr)
+ {
+ if (predic(x))
+ {
+ ++i;
+ }
+ else
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Collections/CollectionUtil.cs b/src/LubanAssistant/Source/Collections/CollectionUtil.cs
new file mode 100644
index 0000000..c468409
--- /dev/null
+++ b/src/LubanAssistant/Source/Collections/CollectionUtil.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace Bright.Collections
+{
+
+ public static class EmptyDictionary
+ {
+ public static Dictionary Empty { get; } = new Dictionary();
+ }
+
+ public static class CollectionUtil
+ {
+
+ public static Dictionary SingletonMap(TK key, TV value)
+ {
+ var newMap = new Dictionary
+ {
+ { key, value }
+ };
+ return newMap;
+ }
+
+ public static string ToString(IDictionary m)
+ {
+ var sb = new StringBuilder();
+ sb.Append('{');
+ foreach (var e in m)
+ {
+ sb.Append(e.Key).Append(':').Append(e.Value).Append(',');
+ }
+ sb.Append('}');
+ return sb.ToString();
+ }
+
+ public static void Replace(IDictionary dest, IDictionary src)
+ {
+ dest.Clear();
+ foreach (var entry in src)
+ {
+ dest.Add(entry.Key, entry.Value);
+ }
+
+ }
+
+ public static void MergeIntValueDic(IDictionary dest, IDictionary src)
+ {
+ foreach (var entry in src)
+ {
+ if (dest.ContainsKey(entry.Key))
+ {
+ dest[entry.Key] = dest[entry.Key] + entry.Value;
+ }
+ else
+ {
+ dest.Add(entry.Key, entry.Value);
+ }
+ }
+ }
+
+ public static void MergeFloatValueDic(IDictionary dest, IDictionary src)
+ {
+ foreach (var entry in src)
+ {
+ if (dest.ContainsKey(entry.Key))
+ {
+ dest[entry.Key] = dest[entry.Key] + entry.Value;
+ }
+ else
+ {
+ dest.Add(entry.Key, entry.Value);
+ }
+ }
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Common/HashUtil.cs b/src/LubanAssistant/Source/Common/HashUtil.cs
new file mode 100644
index 0000000..7f85275
--- /dev/null
+++ b/src/LubanAssistant/Source/Common/HashUtil.cs
@@ -0,0 +1,91 @@
+using System;
+
+namespace Bright.Common
+{
+ ///
+ /// 从 System.Collections.HashTable 源码抄来
+ ///
+ public static class HashUtil
+ {
+
+ private static readonly int[] primes = {
+ 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
+ 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
+ 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
+ 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
+ 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369};
+
+ private const int HashPrime = 101;
+
+ private const int MaxPrimeArrayLength = 0x7FEFFFFD;
+
+ public static int GetPrime(int min)
+ {
+ if (min < 0)
+ throw new ArgumentException();
+
+ for (int i = 0; i < primes.Length; i++)
+ {
+ int prime = primes[i];
+ if (prime >= min) return prime;
+ }
+
+ //outside of our predefined table.
+ //compute the hard way.
+ for (int i = (min | 1); i < int.MaxValue; i += 2)
+ {
+ if (IsPrime(i) && ((i - 1) % HashPrime != 0))
+ return i;
+ }
+ return min;
+ }
+
+ public static bool IsPrime(int candidate)
+ {
+ if ((candidate & 1) != 0)
+ {
+ int limit = (int)Math.Sqrt(candidate);
+ for (int divisor = 3; divisor <= limit; divisor += 2)
+ {
+ if ((candidate % divisor) == 0)
+ return false;
+ }
+ return true;
+ }
+ return (candidate == 2);
+ }
+
+ public static int GetMinPrime()
+ {
+ return primes[0];
+ }
+
+ // Returns size of hashtable to grow to.
+ public static int ExpandPrime(int oldSize)
+ {
+ int newSize = 2 * oldSize;
+
+ // Allow the hashtables to grow to maximum possible size (~2G elements) before encoutering capacity overflow.
+ // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
+ if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
+ {
+ return MaxPrimeArrayLength;
+ }
+
+ return GetPrime(newSize);
+ }
+
+ public static int CalcHash(int x)
+ {
+ int hash = 17;
+ return hash * 23 + x;
+ }
+
+ public static int CalcHash(int x, int y)
+ {
+ int hash = 17;
+ hash = hash * 23 + x;
+ return hash * 23 + y;
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Common/MathUtil.cs b/src/LubanAssistant/Source/Common/MathUtil.cs
new file mode 100644
index 0000000..d01fb56
--- /dev/null
+++ b/src/LubanAssistant/Source/Common/MathUtil.cs
@@ -0,0 +1,106 @@
+namespace Bright.Common
+{
+ public static class MathUtil
+ {
+ ///
+ /// 确保返回 x + y 的正确值, 如果溢出 抛出异常
+ ///
+ ///
+ ///
+ ///
+ public static int AddExactly(int x, int y)
+ {
+ return checked(x + y);
+ }
+
+ ///
+ /// 确保返回 x + y 的正确值, 如果溢出 抛出异常
+ ///
+ ///
+ ///
+ ///
+ public static long AddExactly(long x, long y)
+ {
+ return checked(x + y);
+ }
+
+ ///
+ /// 确保返回 x - y 的正确值, 如果溢出 抛出异常
+ ///
+ ///
+ ///
+ ///
+ public static int SubExcatly(int x, int y)
+ {
+ return checked(x - y);
+ }
+
+ ///
+ /// 确保返回 x - y 的正确值, 如果溢出 抛出异常
+ ///
+ ///
+ ///
+ ///
+ public static long SubExactly(long x, long y)
+ {
+ return checked(x - y);
+ }
+
+ ///
+ /// 确保返回 x * y 的正确值, 如果溢出 抛出异常
+ ///
+ ///
+ ///
+ ///
+ public static int MultifyExactly(int x, int y)
+ {
+ return checked(x * y);
+ }
+
+ ///
+ /// 确保返回 x * y 的正确值, 如果溢出 抛出异常
+ ///
+ ///
+ ///
+ ///
+ public static long MultifyExactly(long x, long y)
+ {
+ return checked(x * y);
+ }
+
+ ///
+ /// 取 >= x/y 的最小整数
+ ///
+ /// 除数
+ /// 被除数
+ ///
+ public static int Ceil(int x, int y)
+ {
+ return (x + y - 1) / y;
+ }
+
+ ///
+ /// 取 >= x/y 的最小整数
+ ///
+ /// 除数
+ /// 被除数
+ ///
+ public static long Ceil(long x, long y)
+ {
+ return (x + y - 1) / y;
+ }
+
+ ///
+ /// 返回 [min, max] 之间的数
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static int Clamp(int value, int min, int max)
+ {
+ return value < min ? min : (value > max ? max : value);
+ }
+
+ }
+}
diff --git a/src/LubanAssistant/Source/Common/SerializationUtil.cs b/src/LubanAssistant/Source/Common/SerializationUtil.cs
new file mode 100644
index 0000000..a724085
--- /dev/null
+++ b/src/LubanAssistant/Source/Common/SerializationUtil.cs
@@ -0,0 +1,214 @@
+using System.Collections.Generic;
+using System.Numerics;
+using Bright.Serialization;
+
+namespace Bright.Common
+{
+ public static class SerializationUtil
+ {
+ public static void Serialize(ByteBuf os, List list) where T : ISerializable
+ {
+ os.WriteSize(list.Count);
+ foreach (var e in list)
+ {
+ e.Serialize(os);
+ }
+ }
+
+ public static void Deserialize(ByteBuf os, List list) where T : ISerializable, new()
+ {
+ int n = os.ReadSize();
+ for (int i = 0; i < n; i++)
+ {
+ T e = new T();
+ e.Deserialize(os);
+ list.Add(e);
+ }
+ }
+
+ public static void Serialize(ByteBuf os, List list)
+ {
+ os.WriteSize(list.Count);
+ foreach (var e in list)
+ {
+ os.WriteString(e);
+ }
+ }
+
+ public static void Deserialize(ByteBuf os, List list)
+ {
+ int n = os.ReadSize();
+ for (int i = 0; i < n; i++)
+ {
+ list.Add(os.ReadString());
+ }
+ }
+
+ public static unsafe int FloatToIntBits(float f)
+ {
+ return *((int*)&f);
+ }
+
+ public static void SerializeBool(ByteBuf buf, bool x)
+ {
+ buf.WriteBool(x);
+ }
+
+ public static bool DeserializeBool(ByteBuf buf)
+ {
+ return buf.ReadBool();
+ }
+
+ public static void SerializeByte(ByteBuf buf, byte x)
+ {
+ buf.WriteByte(x);
+ }
+
+ public static byte DeserializeByte(ByteBuf buf)
+ {
+ return buf.ReadByte();
+ }
+
+ public static void SerializeShort(ByteBuf buf, short x)
+ {
+ buf.WriteShort(x);
+ }
+
+ public static short DeserializeShort(ByteBuf buf)
+ {
+ return buf.ReadShort();
+ }
+
+ public static void SerializeFshort(ByteBuf buf, short x)
+ {
+ buf.WriteFshort(x);
+ }
+
+ public static short DeserializeFshort(ByteBuf buf)
+ {
+ return buf.ReadFshort();
+ }
+
+ public static void SerializeInt(ByteBuf buf, int x)
+ {
+ buf.WriteInt(x);
+ }
+
+ public static int DeserializeInt(ByteBuf buf)
+ {
+ return buf.ReadInt();
+ }
+
+ public static void SerializeFint(ByteBuf buf, int x)
+ {
+ buf.WriteFint(x);
+ }
+
+ public static int DeserializeFint(ByteBuf buf)
+ {
+ return buf.ReadFint();
+ }
+
+ public static void SerializeLong(ByteBuf buf, long x)
+ {
+ buf.WriteLong(x);
+ }
+
+ public static long DeserializeLong(ByteBuf buf)
+ {
+ return buf.ReadLong();
+ }
+
+ public static void SerializeFlong(ByteBuf buf, long x)
+ {
+ buf.WriteFlong(x);
+ }
+
+ public static long DeserializeFlong(ByteBuf buf)
+ {
+ return buf.ReadFlong();
+ }
+
+ public static void SerializeFloat(ByteBuf buf, float x)
+ {
+ buf.WriteFloat(x);
+ }
+
+ public static float DeserializeFloat(ByteBuf buf)
+ {
+ return buf.ReadFloat();
+ }
+
+ public static void SerializeDouble(ByteBuf buf, double x)
+ {
+ buf.WriteDouble(x);
+ }
+
+ public static double DeserializeDouble(ByteBuf buf)
+ {
+ return buf.ReadDouble();
+ }
+
+ public static void SerializeString(ByteBuf buf, string x)
+ {
+ buf.WriteString(x);
+ }
+
+ public static string DeserializeString(ByteBuf buf)
+ {
+ return buf.ReadString();
+ }
+
+ public static void SerializeBytes(ByteBuf buf, byte[] x)
+ {
+ buf.WriteBytes(x);
+ }
+
+ public static byte[] DeserializeBytes(ByteBuf buf)
+ {
+ return buf.ReadBytes();
+ }
+
+ public static void SerializeVector2(ByteBuf buf, Vector2 x)
+ {
+ buf.WriteVector2(x);
+ }
+
+ public static Vector2 DeserializeVector2(ByteBuf buf)
+ {
+ return buf.ReadVector2();
+ }
+
+ public static void SerializeVector3(ByteBuf buf, Vector3 x)
+ {
+ buf.WriteVector3(x);
+ }
+
+ public static Vector3 DeserializeVector3(ByteBuf buf)
+ {
+ return buf.ReadVector3();
+ }
+
+ public static void SerializeVector4(ByteBuf buf, Vector4 x)
+ {
+ buf.WriteVector4(x);
+ }
+
+ public static Vector4 DeserializeVector4(ByteBuf buf)
+ {
+ return buf.ReadVector4();
+ }
+
+ public static void SerializeBean(ByteBuf buf, T x) where T : BeanBase
+ {
+ x.Serialize(buf);
+ }
+
+ public static T DeserializeBean(ByteBuf buf) where T : BeanBase, new()
+ {
+ var x = new T();
+ x.Deserialize(buf);
+ return x;
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Common/StringUtil.cs b/src/LubanAssistant/Source/Common/StringUtil.cs
new file mode 100644
index 0000000..c91f2a1
--- /dev/null
+++ b/src/LubanAssistant/Source/Common/StringUtil.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace Bright.Common
+{
+ public static class StringUtil
+ {
+ public static string ToStr(object o)
+ {
+ return ToStr(o, new StringBuilder());
+ }
+
+ public static string ToStr(object o, StringBuilder sb)
+ {
+ foreach (var p in o.GetType().GetFields())
+ {
+
+ sb.Append($"{p.Name} = {p.GetValue(o)},");
+ }
+
+ foreach (var p in o.GetType().GetProperties())
+ {
+ sb.Append($"{p.Name} = {p.GetValue(o)},");
+ }
+ return sb.ToString();
+ }
+
+ public static string ArrayToString(T[] arr)
+ {
+ return "[" + string.Join(",", arr) + "]";
+ }
+
+
+ public static string CollectionToString(IEnumerable arr)
+ {
+ return "[" + string.Join(",", arr) + "]";
+ }
+
+
+ public static string CollectionToString(IDictionary dic)
+ {
+ var sb = new StringBuilder('{');
+ foreach (var e in dic)
+ {
+ sb.Append(e.Key).Append(':');
+ sb.Append(e.Value).Append(',');
+ }
+ sb.Append('}');
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Common/TimeUtil.cs b/src/LubanAssistant/Source/Common/TimeUtil.cs
new file mode 100644
index 0000000..32a1b91
--- /dev/null
+++ b/src/LubanAssistant/Source/Common/TimeUtil.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Threading;
+
+namespace Bright.Time
+{
+ public static class TimeUtil
+ {
+
+ public static readonly long TIMEZONE_OFFSET_MILLS = (long)TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.FromUnixTimeSeconds(0)).TotalMilliseconds;
+ public static readonly int TIMEZONE_OFFSET = (int)TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.FromUnixTimeSeconds(0)).TotalSeconds;
+
+ public const long DAY_MILLISECONDS = 86400000;
+ public const int DAY_SECONDS = 86400;
+ public const int HOUR_SECONDS = 3600;
+ public const int MINUTE_SECONDS = 60;
+
+ public const int WEEKDAY_OF_19700101 = 3;
+
+ public const long HOUR_MILLISECONDS = 3600000;
+ public const long MINUTE_MILLISECONDS = 60000;
+ public const long WEEK_MILLISECONDS = DAY_MILLISECONDS * 7;
+
+ public static bool IsSameDay(int time1, int time2)
+ {
+ return (time1 + TIMEZONE_OFFSET) / DAY_SECONDS == (time2 + TIMEZONE_OFFSET) / DAY_SECONDS;
+ }
+
+#if DEBUG
+ private static long s_millisTimeOffsetForTest;
+
+ ///
+ /// 用于调整服务器内时间,只对测试版本生效
+ ///
+ public static long MillisTimeOffsetForTest
+ {
+ get
+ {
+ return Interlocked.Read(ref s_millisTimeOffsetForTest);
+ }
+ set
+ {
+ Interlocked.Exchange(ref s_millisTimeOffsetForTest, value);
+ }
+ }
+
+
+ public static long NowMillis => DateTimeOffset.Now.ToUnixTimeMilliseconds() + s_millisTimeOffsetForTest;
+
+ public static int Now => (int)(DateTimeOffset.Now.ToUnixTimeSeconds() + s_millisTimeOffsetForTest / 1000);
+
+#else
+ public static long NowMillis => DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+ public static int Now => (int)DateTimeOffset.Now.ToUnixTimeSeconds();
+#endif
+
+ public static bool IsSameWeek(int time1, int time2)
+ {
+ return GetMondayZeroTimeOfThisWeek(time1) == GetMondayZeroTimeOfThisWeek(time2);
+ }
+
+ public static int TodayZeroTime()
+ {
+ return TodayZeroTime(Now);
+ }
+
+ public static int TodayZeroTime(int time)
+ {
+ return (time + TIMEZONE_OFFSET) / DAY_SECONDS * DAY_SECONDS - TIMEZONE_OFFSET;
+ }
+
+
+ public static int TomorrowZeroTime()
+ {
+ return TomorrowZeroTime(Now);
+ }
+
+ public static int TomorrowZeroTime(int time)
+ {
+ return TodayZeroTime(time) + DAY_SECONDS;
+ }
+
+ public static int AnotherDayZeroTime(int time, int offsetDayNum)
+ {
+ return TodayZeroTime(time) + DAY_SECONDS * offsetDayNum;
+ }
+
+ public static bool IsContinuesDay(int time1, int time2)
+ {
+ return (time1 + TIMEZONE_OFFSET) / DAY_SECONDS + 1 == (time2 + TIMEZONE_OFFSET) / DAY_SECONDS;
+ }
+
+ public static int DayOffset(int time1, int time2)
+ {
+ int a = (time1 + TIMEZONE_OFFSET) / DAY_SECONDS;
+ int b = (time2 + TIMEZONE_OFFSET) / DAY_SECONDS;
+ return Math.Abs(a - b);
+ }
+
+ public static int GetSecondsFromTodayZeroTime(int time)
+ {
+ return time - TodayZeroTime(time);
+ }
+
+ public static int GetHourOfToday(int time)
+ {
+ return (time + TIMEZONE_OFFSET) % DAY_SECONDS / HOUR_SECONDS;
+ }
+
+ public static int GetMinuteOfToday(int time)
+ {
+ long interval = time - TodayZeroTime(time);
+ long left = interval % HOUR_SECONDS;
+ return (int)(left / MINUTE_SECONDS);
+ }
+
+ public static long GetSecondsOfDay(int hour, int minute)
+ {
+ return hour * HOUR_SECONDS + minute * MINUTE_SECONDS;
+ }
+
+ public static int GetWeekDay(int time)
+ {
+ return ((time + TIMEZONE_OFFSET) / DAY_SECONDS + WEEKDAY_OF_19700101) % 7;
+ }
+
+
+ public static int GetMondayZeroTimeOfNextWeek(int time)
+ {
+ return TodayZeroTime(time) + DAY_SECONDS * (7 - GetWeekDay(time));
+ }
+
+ public static int GetMondayZeroTimeOfNextWeek()
+ {
+ return GetMondayZeroTimeOfNextWeek(Now);
+ }
+
+ public static int GetMondayZeroTimeOfThisWeek(int time)
+ {
+ return TodayZeroTime(time) - DAY_SECONDS * GetWeekDay(time);
+ }
+
+ /**
+ * weekday 0 - 6 对应 周一 到 周日
+ */
+ public static long GetSecondsOfWeek(int weekday, int hour, int minute, int second)
+ {
+ return (weekday * 86400 + hour * 3600 + minute * 60 + second) * 1000L;
+ }
+
+ public static long ToMills(float seconds)
+ {
+ return (long)((double)seconds * 1000);
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Common/ValueUtil.cs b/src/LubanAssistant/Source/Common/ValueUtil.cs
new file mode 100644
index 0000000..3b16bd2
--- /dev/null
+++ b/src/LubanAssistant/Source/Common/ValueUtil.cs
@@ -0,0 +1,22 @@
+namespace Bright.Common
+{
+ public static class ValueUtil
+ {
+ public static void Swap(ref T a, ref T b)
+ {
+ T temp = a;
+ a = b;
+ b = temp;
+ }
+
+ public static void LockAndSwap(object locker, ref T a, ref T b)
+ {
+ lock (locker)
+ {
+ T temp = a;
+ a = b;
+ b = temp;
+ }
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/DataSources/DataSourceFactory.cs b/src/LubanAssistant/Source/DataSources/DataSourceFactory.cs
new file mode 100644
index 0000000..65457da
--- /dev/null
+++ b/src/LubanAssistant/Source/DataSources/DataSourceFactory.cs
@@ -0,0 +1,43 @@
+using System;
+using System.IO;
+
+namespace Luban.Job.Cfg.DataSources
+{
+ static class DataSourceFactory
+ {
+ public static readonly string[] validDataSourceSuffixes = new string[]
+ {
+ ".xlsx",
+ ".xls",
+ ".csv",
+ ".xml",
+ ".lua",
+ ".json",
+ ".yml",
+ ".bin",
+ };
+
+ public static AbstractDataSource Create(string url, string sheetName, Stream stream)
+ {
+ try
+ {
+ string ext = url.Contains(".") ? Path.GetExtension(url)?.Substring(1) : url;
+ AbstractDataSource source;
+ switch (ext)
+ {
+ case "csv":
+ case "xls":
+ case "xlsx": source = new Excel.ExcelDataSource(); break;
+ case "json": source = new Json.JsonDataSource(); break;
+ default: throw new Exception($"不支持的文件类型:{url}");
+ }
+ source.Load(url, sheetName, stream);
+ return source;
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"文件{url} 加载失败", e);
+ }
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Datas/DText.cs b/src/LubanAssistant/Source/Datas/DText.cs
new file mode 100644
index 0000000..c883d0a
--- /dev/null
+++ b/src/LubanAssistant/Source/Datas/DText.cs
@@ -0,0 +1,54 @@
+using Luban.Job.Cfg.DataVisitors;
+
+namespace Luban.Job.Cfg.Datas
+{
+ public class DText : DType
+ {
+ public const string KEY_NAME = "key";
+ public const string TEXT_NAME = "text";
+
+ public string Key { get; }
+
+ private readonly string _rawValue;
+
+ public string RawValue => _rawValue;
+
+ public override string TypeName => "text";
+
+ public DText(string key, string x)
+ {
+ Key = key;
+ _rawValue = x;
+ }
+
+ public override void Apply(IDataActionVisitor visitor, T x)
+ {
+ visitor.Accept(this, x);
+ }
+
+ public override void Apply(IDataActionVisitor visitor, T1 x, T2 y)
+ {
+ visitor.Accept(this, x, y);
+ }
+
+ public override TR Apply(IDataFuncVisitor
visitor)
+ {
+ return visitor.Accept(this);
+ }
+
+ public override TR Apply(IDataFuncVisitor visitor, T x)
+ {
+ return visitor.Accept(this, x);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is DText o && o._rawValue == this._rawValue && o.Key == this.Key;
+ }
+
+ public override int GetHashCode()
+ {
+ return _rawValue.GetHashCode();
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Datas/DType.cs b/src/LubanAssistant/Source/Datas/DType.cs
new file mode 100644
index 0000000..7a8399b
--- /dev/null
+++ b/src/LubanAssistant/Source/Datas/DType.cs
@@ -0,0 +1,28 @@
+
+using Luban.Job.Cfg.DataVisitors;
+
+namespace Luban.Job.Cfg.Datas
+{
+ public abstract class DType
+ {
+ public abstract void Apply(IDataActionVisitor visitor, T x);
+
+ public abstract void Apply(IDataActionVisitor visitor, T1 x, T2 y);
+
+ public abstract TR Apply(IDataFuncVisitor
visitor);
+
+ public abstract TR Apply(IDataFuncVisitor visitor, T x);
+
+ public abstract string TypeName { get; }
+ }
+
+ public abstract class DType : DType
+ {
+ public T Value { get; }
+
+ protected DType(T value)
+ {
+ Value = value;
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Defs/CfgDefLoader.cs b/src/LubanAssistant/Source/Defs/CfgDefLoader.cs
new file mode 100644
index 0000000..d178997
--- /dev/null
+++ b/src/LubanAssistant/Source/Defs/CfgDefLoader.cs
@@ -0,0 +1,471 @@
+using Bright.Collections;
+using Luban.Common.Utils;
+using Luban.Job.Cfg.Datas;
+using Luban.Job.Cfg.DataSources.Excel;
+using Luban.Job.Cfg.RawDefs;
+using Luban.Job.Cfg.Utils;
+using Luban.Job.Common.Defs;
+using Luban.Job.Common.RawDefs;
+using Luban.Job.Common.Types;
+using Luban.Job.Common.Utils;
+using Luban.Server.Common;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace Luban.Job.Cfg.Defs
+{
+ class CfgDefLoader : CommonDefLoader
+ {
+ private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
+
+ private readonly List _importExcelTableFiles = new();
+ private readonly List _importExcelEnumFiles = new();
+ private readonly List _importExcelBeanFiles = new();
+
+
+ private readonly List _patches = new();
+
+ private readonly List _cfgTables = new List();
+
+ private readonly List _cfgServices = new List();
+
+ private readonly List _cfgGroups = new List();
+
+ private readonly List _defaultGroups = new List();
+
+ public CfgDefLoader(IAgent agent) : base(agent)
+ {
+ RegisterRootDefineHandler("importexcel", AddImportExcel);
+ RegisterRootDefineHandler("patch", AddPatch);
+ RegisterRootDefineHandler("service", AddService);
+ RegisterRootDefineHandler("group", AddGroup);
+
+ RegisterModuleDefineHandler("table", AddTable);
+
+
+ IsBeanFieldMustDefineId = false;
+ }
+
+ public Defines BuildDefines()
+ {
+ return new Defines()
+ {
+ TopModule = TopModule,
+ Patches = _patches,
+ Enums = _enums,
+ Beans = _beans,
+ Tables = _cfgTables,
+ Services = _cfgServices,
+ Groups = _cfgGroups,
+ };
+ }
+
+ private static readonly List _excelImportRequireAttrs = new List { "name", "type" };
+ private void AddImportExcel(XElement e)
+ {
+ ValidAttrKeys(RootXml, e, null, _excelImportRequireAttrs);
+ var importName = XmlUtil.GetRequiredAttribute(e, "name");
+ if (string.IsNullOrWhiteSpace(importName))
+ {
+ throw new Exception("importexcel 属性name不能为空");
+ }
+ var type = XmlUtil.GetRequiredAttribute(e, "type");
+ if (string.IsNullOrWhiteSpace(type))
+ {
+ throw new Exception($"importexcel name:'{importName}' type属性不能为空");
+ }
+ switch (type)
+ {
+ case "table": this._importExcelTableFiles.Add(importName); break;
+ case "enum": this._importExcelEnumFiles.Add(importName); break;
+ case "bean": this._importExcelBeanFiles.Add(importName); break;
+ default: throw new Exception($"importexcel name:'{importName}' type:'{type}' 不合法. 有效值为 table|enum|bean");
+ }
+ }
+
+ private static readonly List _patchRequireAttrs = new List { "name" };
+ private void AddPatch(XElement e)
+ {
+ ValidAttrKeys(RootXml, e, null, _patchRequireAttrs);
+ var patchName = e.Attribute("name").Value;
+ if (string.IsNullOrWhiteSpace(patchName))
+ {
+ throw new Exception("patch 属性name不能为空");
+ }
+ if (this._patches.Any(b => b.Name == patchName))
+ {
+ throw new Exception($"patch '{patchName}' 重复");
+ }
+ _patches.Add(new Patch(patchName));
+ }
+
+ private static readonly List _groupOptionalAttrs = new List { "default" };
+ private static readonly List _groupRequireAttrs = new List { "name" };
+
+ private void AddGroup(XElement e)
+ {
+ ValidAttrKeys(RootXml, e, _groupOptionalAttrs, _groupRequireAttrs);
+ List groupNames = CreateGroups(e.Attribute("name").Value);
+
+ foreach (var g in groupNames)
+ {
+ if (_cfgGroups.Any(cg => cg.Names.Contains(g)))
+ {
+ throw new Exception($"group名:'{g}' 重复");
+ }
+ }
+
+ if (XmlUtil.GetOptionBoolAttribute(e, "default"))
+ {
+ this._defaultGroups.AddRange(groupNames);
+ }
+ _cfgGroups.Add(new Group() { Names = groupNames });
+ }
+
+ private void FillValueValidator(CfgField f, string attrValue, string validatorName)
+ {
+ if (!string.IsNullOrWhiteSpace(attrValue))
+ {
+ var validator = new Validator() { Type = validatorName, Rule = attrValue };
+ f.Validators.Add(validator);
+ f.ValueValidators.Add(validator);
+ }
+ }
+
+ private void FillValidators(string defineFile, string key, string attr, List result)
+ {
+ if (!string.IsNullOrWhiteSpace(attr))
+ {
+#if !LUBAN_ASSISTANT
+ foreach (var validatorStr in attr.Split('#', StringSplitOptions.RemoveEmptyEntries))
+#else
+ foreach (var validatorStr in attr.Split('#'))
+#endif
+ {
+ var sepIndex = validatorStr.IndexOf(':');
+ if (sepIndex <= 0)
+ {
+ throw new Exception($"定义文件:{defineFile} key:'{key}' attr:'{attr}' 不是合法的 validator 定义 (key1:value1#key2:value2 ...)");
+ }
+#if !LUBAN_ASSISTANT
+ result.Add(new Validator() { Type = validatorStr[..sepIndex], Rule = validatorStr[(sepIndex + 1)..] });
+#else
+ result.Add(new Validator() { Type = validatorStr.Substring(0, sepIndex), Rule = validatorStr.Substring(sepIndex + 1, validatorStr.Length - sepIndex - 1) });
+#endif
+ }
+ }
+ }
+
+ private readonly List _serviceAttrs = new List { "name", "manager", "group" };
+
+ private void AddService(XElement e)
+ {
+ var name = XmlUtil.GetRequiredAttribute(e, "name");
+ var manager = XmlUtil.GetRequiredAttribute(e, "manager");
+ List groups = CreateGroups(XmlUtil.GetOptionalAttribute(e, "group"));
+ var refs = new List();
+
+ s_logger.Trace("service name:{name} manager:{manager}", name, manager);
+ ValidAttrKeys(RootXml, e, _serviceAttrs, _serviceAttrs);
+ foreach (XElement ele in e.Elements())
+ {
+ string tagName = ele.Name.LocalName;
+ s_logger.Trace("service {service_name} tag: {name} {value}", name, tagName, ele);
+ switch (tagName)
+ {
+ case "ref":
+ {
+ refs.Add(XmlUtil.GetRequiredAttribute(ele, "name"));
+ break;
+ }
+ default:
+ {
+ throw new Exception($"service:'{name}' tag:'{tagName}' 非法");
+ }
+ }
+ }
+ if (!ValidGroup(groups, out var invalidGroup))
+ {
+ throw new Exception($"service:'{name}' group:'{invalidGroup}' 不存在");
+ }
+ _cfgServices.Add(new Service() { Name = name, Manager = manager, Groups = groups, Refs = refs });
+ }
+
+
+ private readonly Dictionary _name2CfgTable = new Dictionary();
+
+ private static List CreateGroups(string s)
+ {
+ return s.Split(',', ';').Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
+ }
+
+ private bool ValidGroup(List groups, out string invalidGroup)
+ {
+ foreach (var g in groups)
+ {
+ if (!this._cfgGroups.Any(cg => cg.Names.Contains(g)))
+ {
+ invalidGroup = g;
+ return false;
+ }
+ }
+ invalidGroup = null;
+ return true;
+ }
+
+ private ETableMode ConvertMode(string defineFile, string tableName, string modeStr, string indexStr)
+ {
+ ETableMode mode;
+ switch (modeStr)
+ {
+ case "one":
+ {
+ if (!string.IsNullOrWhiteSpace(indexStr))
+ {
+ throw new Exception($"定义文件:{defineFile} table:'{tableName}' mode=one 是单例表,不支持定义index属性");
+ }
+ mode = ETableMode.ONE;
+ break;
+ }
+ case "map":
+ {
+ //if ((string.IsNullOrWhiteSpace(indexStr) || indexStr.Split(',').Length != 1))
+ //{
+ // throw new Exception($"定义文件:{CurImportFile} table:{tableName} 是单键表,必须在index属性里指定1个key");
+ //}
+ mode = ETableMode.MAP;
+ break;
+ }
+ case "":
+ {
+ mode = ETableMode.MAP;
+ break;
+ }
+ default:
+ {
+ throw new ArgumentException($"不支持的 mode:{modeStr}");
+ }
+ }
+ return mode;
+ }
+
+ private readonly List _tableOptionalAttrs = new List { "index", "mode", "group", "patch_input", "comment", "define_from_file" };
+ private readonly List _tableRequireAttrs = new List { "name", "value", "input" };
+
+ private void AddTable(string defineFile, XElement e)
+ {
+ ValidAttrKeys(defineFile, e, _tableOptionalAttrs, _tableRequireAttrs);
+ string name = XmlUtil.GetRequiredAttribute(e, "name");
+ string module = CurNamespace;
+ string valueType = XmlUtil.GetRequiredAttribute(e, "value");
+ bool defineFromFile = XmlUtil.GetOptionBoolAttribute(e, "define_from_file");
+ string index = XmlUtil.GetOptionalAttribute(e, "index");
+ string group = XmlUtil.GetOptionalAttribute(e, "group");
+ string comment = XmlUtil.GetOptionalAttribute(e, "comment");
+ string input = XmlUtil.GetRequiredAttribute(e, "input");
+ string patchInput = XmlUtil.GetOptionalAttribute(e, "patch_input");
+ string mode = XmlUtil.GetOptionalAttribute(e, "mode");
+ string tags = XmlUtil.GetOptionalAttribute(e, "tags");
+ AddTable(defineFile, name, module, valueType, index, mode, group, comment, defineFromFile, input, patchInput, tags);
+ }
+
+ private void AddTable(string defineFile, string name, string module, string valueType, string index, string mode, string group,
+ string comment, bool defineFromExcel, string input, string patchInput, string tags)
+ {
+ var p = new Table()
+ {
+ Name = name,
+ Namespace = module,
+ ValueType = valueType,
+ LoadDefineFromFile = defineFromExcel,
+ Index = index,
+ Groups = CreateGroups(group),
+ Comment = comment,
+ Mode = ConvertMode(defineFile, name, mode, index),
+ Tags = tags,
+ };
+
+ if (p.Groups.Count == 0)
+ {
+ p.Groups = this._defaultGroups;
+ }
+ else if (!ValidGroup(p.Groups, out var invalidGroup))
+ {
+ throw new Exception($"定义文件:{defineFile} table:'{p.Name}' group:'{invalidGroup}' 不存在");
+ }
+ p.InputFiles.AddRange(input.Split(',').Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s)));
+
+ if (!string.IsNullOrWhiteSpace(patchInput))
+ {
+ foreach (var subPatchStr in patchInput.Split('|').Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s)))
+ {
+ var nameAndDirs = subPatchStr.Split(':');
+ if (nameAndDirs.Length != 2)
+ {
+ throw new Exception($"定义文件:{defineFile} table:'{p.Name}' patch_input:'{subPatchStr}' 定义不合法");
+ }
+ var patchDirs = nameAndDirs[1].Split(',', ';').ToList();
+ if (!p.PatchInputFiles.TryAdd(nameAndDirs[0], patchDirs))
+ {
+ throw new Exception($"定义文件:{defineFile} table:'{p.Name}' patch_input:'{subPatchStr}' 子patch:'{nameAndDirs[0]}' 重复");
+ }
+ }
+ }
+
+ _cfgTables.Add(p);
+ }
+
+
+ private static readonly List _fieldOptionalAttrs = new()
+ {
+ "index",
+ "sep",
+ "validator",
+ "key_validator",
+ "value_validator",
+ "ref",
+ "path",
+ "range",
+ "multi_rows",
+ "group",
+ "res",
+ "convert",
+ "comment",
+ "tags",
+ "default",
+ "orientation",
+ };
+
+ private static readonly List _fieldRequireAttrs = new List { "name", "type" };
+
+ protected override Field CreateField(string defineFile, XElement e)
+ {
+ ValidAttrKeys(defineFile, e, _fieldOptionalAttrs, _fieldRequireAttrs);
+
+ return CreateField(defineFile, XmlUtil.GetRequiredAttribute(e, "name"),
+ XmlUtil.GetRequiredAttribute(e, "type"),
+ XmlUtil.GetOptionalAttribute(e, "index"),
+ XmlUtil.GetOptionalAttribute(e, "sep"),
+ XmlUtil.GetOptionBoolAttribute(e, "multi_rows"),
+ XmlUtil.GetOptionalAttribute(e, "group"),
+ XmlUtil.GetOptionalAttribute(e, "res"),
+ XmlUtil.GetOptionalAttribute(e, "convert"),
+ XmlUtil.GetOptionalAttribute(e, "comment"),
+ XmlUtil.GetOptionalAttribute(e, "ref"),
+ XmlUtil.GetOptionalAttribute(e, "path"),
+ XmlUtil.GetOptionalAttribute(e, "range"),
+ XmlUtil.GetOptionalAttribute(e, "key_validator"),
+ XmlUtil.GetOptionalAttribute(e, "value_validator"),
+ XmlUtil.GetOptionalAttribute(e, "validator"),
+ XmlUtil.GetOptionalAttribute(e, "tags"),
+ false,
+ DefUtil.ParseOrientation(XmlUtil.GetOptionalAttribute(e, "orientation"))
+ );
+ }
+
+ private Field CreateField(string defileFile, string name, string type, string index, string sep, bool isMultiRow, string group, string resource, string converter,
+ string comment, string refs, string path, string range, string keyValidator, string valueValidator, string validator, string tags,
+ bool ignoreNameValidation, bool isRowOrient)
+ {
+ var f = new CfgField()
+ {
+ Name = name,
+ Index = index,
+ Sep = sep,
+ IsMultiRow = isMultiRow,
+ Groups = CreateGroups(group),
+ Resource = resource,
+ Converter = converter,
+ Comment = comment,
+ Tags = tags,
+ IgnoreNameValidation = ignoreNameValidation,
+ IsRowOrient = isRowOrient,
+ };
+
+ // 字段与table的默认组不一样。
+ // table 默认只属于default=1的组
+ // 字段默认属于所有组
+ if (f.Groups.Count == 0)
+ {
+
+ }
+ else if (!ValidGroup(f.Groups, out var invalidGroup))
+ {
+ throw new Exception($"定义文件:{defileFile} field:'{name}' group:'{invalidGroup}' 不存在");
+ }
+ f.Type = type;
+
+
+ FillValueValidator(f, refs, "ref");
+ FillValueValidator(f, path, "path"); // (ue4|unity|normal|regex);xxx;xxx
+ FillValueValidator(f, range, "range");
+
+ FillValidators(defileFile, "key_validator", keyValidator, f.KeyValidators);
+ FillValidators(defileFile, "value_validator", valueValidator, f.ValueValidators);
+ FillValidators(defileFile, "validator", validator, f.Validators);
+ return f;
+ }
+
+ private static readonly List _beanOptinsAttrs = new List { "value_type", "alias", "sep", "comment", "tags" };
+ private static readonly List _beanRequireAttrs = new List { "name" };
+
+ protected override void AddBean(string defineFile, XElement e, string parent)
+ {
+ ValidAttrKeys(defineFile, e, _beanOptinsAttrs, _beanRequireAttrs);
+
+ var b = new CfgBean()
+ {
+ Name = XmlUtil.GetRequiredAttribute(e, "name"),
+ Namespace = CurNamespace,
+ Parent = parent.Length > 0 ? parent : "",
+ TypeId = 0,
+ IsSerializeCompatible = true,
+ IsValueType = XmlUtil.GetOptionBoolAttribute(e, "value_type"),
+ Alias = XmlUtil.GetOptionalAttribute(e, "alias"),
+ Sep = XmlUtil.GetOptionalAttribute(e, "sep"),
+ Comment = XmlUtil.GetOptionalAttribute(e, "comment"),
+ Tags = XmlUtil.GetOptionalAttribute(e, "tags"),
+ };
+ var childBeans = new List();
+
+ bool defineAnyChildBean = false;
+ foreach (XElement fe in e.Elements())
+ {
+ switch (fe.Name.LocalName)
+ {
+ case "var":
+ {
+ if (defineAnyChildBean)
+ {
+ throw new LoadDefException($"定义文件:{defineFile} 类型:{b.FullName} 的多态子bean必须在所有成员字段 之前定义");
+ }
+ b.Fields.Add(CreateField(defineFile, fe)); ;
+ break;
+ }
+ case "bean":
+ {
+ defineAnyChildBean = true;
+ childBeans.Add(fe);
+ break;
+ }
+ default:
+ {
+ throw new LoadDefException($"定义文件:{defineFile} 类型:{b.FullName} 不支持 tag:{fe.Name}");
+ }
+ }
+ }
+ s_logger.Trace("add bean:{@bean}", b);
+ _beans.Add(b);
+
+ var fullname = b.FullName;
+ foreach (var cb in childBeans)
+ {
+ AddBean(defineFile, cb, fullname);
+ }
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Defs/DefAssembly.cs b/src/LubanAssistant/Source/Defs/DefAssembly.cs
new file mode 100644
index 0000000..250a29f
--- /dev/null
+++ b/src/LubanAssistant/Source/Defs/DefAssembly.cs
@@ -0,0 +1,290 @@
+using Bright.Collections;
+using Luban.Job.Cfg.Datas;
+using Luban.Job.Cfg.RawDefs;
+using Luban.Job.Cfg.TypeVisitors;
+using Luban.Job.Common.Defs;
+using Luban.Server.Common;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Luban.Job.Cfg.Defs
+{
+ public class TableDataInfo
+ {
+ public List MainRecords { get; }
+
+ public List PatchRecords { get; }
+
+ public List FinalRecords { get; set; }
+
+ public Dictionary FinalRecordMap { get; set; }
+
+ public TableDataInfo(List mainRecords, List patchRecords)
+ {
+ MainRecords = mainRecords;
+ PatchRecords = patchRecords;
+ }
+ }
+
+ public class DefAssembly : DefAssemblyBase
+ {
+ private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
+
+ public new static DefAssembly LocalAssebmly { get => (DefAssembly)DefAssemblyBase.LocalAssebmly; set => DefAssemblyBase.LocalAssebmly = value; }
+
+ public Service CfgTargetService { get; private set; }
+
+ private readonly string _patchName;
+ private readonly List _excludeTags;
+
+ public Patch TargetPatch { get; private set; }
+
+ public TimeZoneInfo TimeZone { get; }
+
+ public DefAssembly(string patchName, TimeZoneInfo timezone, List excludeTags, IAgent agent)
+ {
+ this._patchName = patchName;
+ this.TimeZone = timezone;
+ this._excludeTags = excludeTags;
+ this.Agent = agent;
+ }
+
+ public bool NeedExport(List groups)
+ {
+ if (groups.Count == 0)
+ {
+ return true;
+ }
+ return groups.Any(g => CfgTargetService.Groups.Contains(g));
+ }
+
+ private readonly List _patches = new List();
+
+ private readonly List _cfgServices = new List();
+
+ private readonly ConcurrentDictionary _recordsByTables = new();
+
+ public Dictionary CfgTables { get; } = new Dictionary();
+
+ public Patch GetPatch(string name)
+ {
+ return _patches.Find(b => b.Name == name);
+ }
+
+ public void AddCfgTable(DefTable table)
+ {
+ if (!CfgTables.TryAdd(table.FullName, table))
+ {
+ throw new Exception($"table:'{table.FullName}' duplicated");
+ }
+ }
+
+ public DefTable GetCfgTable(string name)
+ {
+ return CfgTables.TryGetValue(name, out var t) ? t : null;
+ }
+
+ public void AddDataTable(DefTable table, List mainRecords, List patchRecords)
+ {
+ _recordsByTables[table.FullName] = new TableDataInfo(mainRecords, patchRecords);
+ }
+
+ public List GetTableAllDataList(DefTable table)
+ {
+ return _recordsByTables[table.FullName].FinalRecords;
+ }
+
+ public List GetTableExportDataList(DefTable table)
+ {
+ var tableDataInfo = _recordsByTables[table.FullName];
+ if (_excludeTags.Count == 0)
+ {
+ return tableDataInfo.FinalRecords;
+ }
+ else
+ {
+ var finalRecords = tableDataInfo.FinalRecords.Where(r => r.IsNotFiltered(_excludeTags)).ToList();
+ if (table.IsOneValueTable && finalRecords.Count != 1)
+ {
+ throw new Exception($"配置表 {table.FullName} 是单值表 mode=one,但数据个数:{finalRecords.Count} != 1");
+ }
+ return finalRecords;
+ }
+ }
+
+ public TableDataInfo GetTableDataInfo(DefTable table)
+ {
+ return _recordsByTables[table.FullName];
+ }
+
+ public List GetExportTables()
+ {
+ return Types.Values.Where(t => t is DefTable ct && ct.NeedExport).Select(t => (DefTable)t).ToList();
+ }
+
+ public List GetExportTypes()
+ {
+ var refTypes = new Dictionary();
+ var targetService = CfgTargetService;
+ foreach (var refType in targetService.Refs)
+ {
+ if (!this.Types.ContainsKey(refType))
+ {
+ throw new Exception($"service:'{targetService.Name}' ref:'{refType}' 类型不存在");
+ }
+ if (!refTypes.TryAdd(refType, this.Types[refType]))
+ {
+ throw new Exception($"service:'{targetService.Name}' ref:'{refType}' 重复引用");
+ }
+ }
+ foreach (var e in this.Types)
+ {
+ if (!refTypes.ContainsKey(e.Key) && (e.Value is DefEnum))
+ {
+ refTypes.Add(e.Key, e.Value);
+ }
+ }
+
+ foreach (var table in GetExportTables())
+ {
+ refTypes[table.FullName] = table;
+ table.ValueTType.Apply(RefTypeVisitor.Ins, refTypes);
+ }
+
+ return refTypes.Values.ToList();
+ }
+
+ public void Load(string outputService, Defines defines)
+ {
+ SupportDatetimeType = true;
+
+ TopModule = defines.TopModule;
+
+ CfgTargetService = defines.Services.Find(s => s.Name == outputService);
+
+ if (CfgTargetService == null)
+ {
+ throw new ArgumentException($"service:{outputService} not exists");
+ }
+
+ if (!string.IsNullOrWhiteSpace(_patchName))
+ {
+ TargetPatch = defines.Patches.Find(b => b.Name == _patchName);
+ if (TargetPatch == null)
+ {
+ throw new Exception($"patch '{_patchName}' not in valid patch set");
+ }
+ }
+
+ this._patches.AddRange(defines.Patches);
+
+ foreach (var e in defines.Enums)
+ {
+ AddType(new DefEnum(e));
+ }
+
+ foreach (var b in defines.Beans)
+ {
+ AddType(new DefBean((CfgBean)b));
+ }
+
+ foreach (var p in defines.Tables)
+ {
+ var table = new DefTable(p);
+ AddType(table);
+ AddCfgTable(table);
+ }
+
+ _cfgServices.AddRange(defines.Services);
+
+ foreach (var type in Types.Values)
+ {
+ type.AssemblyBase = this;
+ }
+
+ foreach (var type in Types.Values)
+ {
+ try
+ {
+ s_logger.Trace("precompile type:{0} begin", type.FullName);
+ type.PreCompile();
+ s_logger.Trace("precompile type:{0} end", type.FullName);
+ }
+ catch (Exception)
+ {
+ this.Agent.Error("precompile type:{0} error", type.FullName);
+ throw;
+ }
+ }
+ foreach (var type in Types.Values)
+ {
+ try
+ {
+ s_logger.Trace("compile type:{0} begin", type.FullName);
+ type.Compile();
+ s_logger.Trace("compile type:{0} end", type.FullName);
+ }
+ catch (Exception)
+ {
+ this.Agent.Error("compile type:{0} error", type.FullName);
+ s_logger.Error("compile type:{0} error", type.FullName);
+ throw;
+ }
+ }
+ foreach (var type in Types.Values)
+ {
+ try
+ {
+ s_logger.Trace("post compile type:{0} begin", type.FullName);
+ type.PostCompile();
+ s_logger.Trace("post compile type:{0} end", type.FullName);
+ }
+ catch (Exception)
+ {
+ this.Agent.Error("post compile type:{0} error", type.FullName);
+ s_logger.Error("post compile type:{0} error", type.FullName);
+ throw;
+ }
+ }
+
+ // 丑陋. 怎么写更好?
+
+ // 递归 设置DefBean及DefField 的 IsMultiRow
+
+ MarkMultiRows();
+ }
+
+ public void MarkMultiRows()
+ {
+ var multiRowBeans = new HashSet();
+ for (bool anyMark = true; anyMark;)
+ {
+ anyMark = false;
+ foreach (var type in this.Types.Values)
+ {
+ if (type is DefBean beanType && !beanType.IsMultiRow)
+ {
+ bool isMultiRows;
+ if (beanType.IsNotAbstractType)
+ {
+ isMultiRows = beanType.HierarchyFields.Any(f => ((DefField)f).ComputeIsMultiRow());
+ }
+ else
+ {
+ isMultiRows = beanType.HierarchyNotAbstractChildren.Any(c => ((DefBean)c).IsMultiRow);
+ }
+ if (isMultiRows)
+ {
+ beanType.IsMultiRow = true;
+ //s_logger.Info("bean:{bean} is multi row", beanType.FullName);
+ anyMark = true;
+ }
+ }
+ }
+
+ }
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Defs/DefField.cs b/src/LubanAssistant/Source/Defs/DefField.cs
new file mode 100644
index 0000000..cccacb5
--- /dev/null
+++ b/src/LubanAssistant/Source/Defs/DefField.cs
@@ -0,0 +1,181 @@
+using Luban.Common.Utils;
+using Luban.Job.Cfg.DataCreators;
+using Luban.Job.Cfg.Datas;
+using Luban.Job.Cfg.RawDefs;
+using Luban.Job.Common.Defs;
+using Luban.Job.Common.Types;
+using Luban.Job.Common.TypeVisitors;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Luban.Job.Cfg.Defs
+{
+ public class DefField : DefFieldBase
+ {
+ public DefAssembly Assembly => (DefAssembly)HostType.AssemblyBase;
+
+
+ public bool RawIsMultiRow { get; }
+
+ public bool IsMultiRow { get; private set; }
+
+ public bool ComputeIsMultiRow()
+ {
+ if (IsMultiRow)
+ {
+ return true;
+ }
+
+ switch (CType)
+ {
+ case TBean b: { return IsMultiRow = ((DefBean)b.Bean).IsMultiRow; }
+ case TList b: { return IsMultiRow = b.ElementType is TBean b2 && ((DefBean)b2.Bean).IsMultiRow; }
+ case TArray b: { return IsMultiRow = b.ElementType is TBean b2 && ((DefBean)b2.Bean).IsMultiRow; }
+ case TMap b: { return IsMultiRow = b.ValueType is TBean b2 && ((DefBean)b2.Bean).IsMultiRow; }
+ default: return false;
+ }
+ }
+
+
+ public string Index { get; }
+
+ public List Groups { get; }
+
+ public DefField IndexField { get; private set; }
+
+
+ public string Sep { get; set; }
+
+ // 如果没有指定sep
+ // 如果是bean,且指定了sep,则使用此值
+ // 如果是vectorN,使用 ,
+ public string ActualSep => string.IsNullOrWhiteSpace(Sep) ? (CType is TBean bean ? ((DefBean)bean.Bean).Sep : "") : Sep;
+
+ public bool NeedExport => Assembly.NeedExport(this.Groups);
+
+ public TEnum Remapper { get; private set; }
+
+ public CfgField RawDefine { get; }
+
+ public string GetTextKeyName(string name) => name + TText.L10N_FIELD_SUFFIX;
+
+ public bool GenTextKey => this.CType is TText;
+
+
+ public string DefaultValue { get; }
+
+ public DType DefalutDtypeValue { get; private set; }
+
+ public bool IsRowOrient { get; }
+
+ public DefField(DefTypeBase host, CfgField f, int idOffset) : base(host, f, idOffset)
+ {
+ Index = f.Index;
+ Sep = f.Sep;
+ this.IsMultiRow = this.RawIsMultiRow = f.IsMultiRow;
+ this.Groups = f.Groups;
+ this.RawDefine = f;
+ this.DefaultValue = f.DefaultValue;
+ this.IsRowOrient = f.IsRowOrient;
+ }
+
+ public override void Compile()
+ {
+ base.Compile();
+
+ if (!string.IsNullOrWhiteSpace(this.DefaultValue))
+ {
+ this.DefalutDtypeValue = CType.Apply(StringDataCreator.Ins, this.DefaultValue);
+ }
+
+ switch (CType)
+ {
+ case TArray t:
+ {
+ if (t.ElementType is TBean e && !e.IsDynamic && e.Bean.HierarchyFields.Count == 0)
+ {
+ throw new Exception($"container element type:'{e.Bean.FullName}' can't be empty bean");
+ }
+ if (t.ElementType is TText)
+ {
+ throw new Exception($"bean:{HostType.FullName} field:{Name} container element type can't text");
+ }
+ break;
+ }
+ case TList t:
+ {
+ if (t.ElementType is TBean e && !e.IsDynamic && e.Bean.HierarchyFields.Count == 0)
+ {
+ throw new Exception($"container element type:'{e.Bean.FullName}' can't be empty bean");
+ }
+ if (t.ElementType is TText)
+ {
+ throw new Exception($"bean:{HostType.FullName} field:{Name} container element type can't text");
+ }
+ break;
+ }
+ case TSet t:
+ {
+ if (t.ElementType is TText)
+ {
+ throw new Exception($"bean:{HostType.FullName} field:{Name} container element type can't text");
+ }
+ break;
+ }
+ case TMap t:
+ {
+ if (t.KeyType is TText)
+ {
+ throw new Exception($"bean:{HostType.FullName} field:{Name} container key type can't text");
+ }
+ if (t.ValueType is TText)
+ {
+ throw new Exception($"bean:{HostType.FullName} field:{Name} container value type can't text");
+ }
+ break;
+ }
+ }
+
+ if (IsMultiRow && !CType.IsCollection && !CType.IsBean)
+ {
+ throw new Exception($"只有容器类型才支持 multi_line 属性");
+ }
+
+ if (string.IsNullOrEmpty(Sep) && CType is TBean bean)
+ {
+ Sep = bean.GetBeanAs().Sep;
+ }
+ if (!string.IsNullOrEmpty(Index))
+ {
+ if ((CType is TArray tarray) && (tarray.ElementType is TBean b))
+ {
+ if ((IndexField = b.GetBeanAs().GetField(Index)) == null)
+ {
+ throw new Exception($"type:'{HostType.FullName}' field:'{Name}' index:'{Index}'. index not exist");
+ }
+ }
+ else if ((CType is TList tlist) && (tlist.ElementType is TBean tb))
+ {
+ if ((IndexField = tb.GetBeanAs().GetField(Index)) == null)
+ {
+ throw new Exception($"type:'{HostType.FullName}' field:'{Name}' index:'{Index}'. index not exist");
+ }
+ }
+ else
+ {
+ throw new Exception($"type:'{HostType.FullName}' field:'{Name}' index:'{Index}'. only array:bean or list:bean support index");
+ }
+ }
+
+ if (!string.IsNullOrEmpty(this.RawDefine.Converter))
+ {
+ this.Remapper = AssemblyBase.GetDefTType(HostType.Namespace, this.RawDefine.Converter, this.IsNullable) as TEnum;
+ if (this.Remapper == null)
+ {
+ throw new Exception($"type:'{HostType.FullName}' field:'{Name}' converter:'{this.RawDefine.Converter}' not exists");
+ }
+ }
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Defs/DefTypeBase.cs b/src/LubanAssistant/Source/Defs/DefTypeBase.cs
new file mode 100644
index 0000000..51d7bcf
--- /dev/null
+++ b/src/LubanAssistant/Source/Defs/DefTypeBase.cs
@@ -0,0 +1,69 @@
+using Luban.Common.Utils;
+using Luban.Server.Common;
+using System.Collections.Generic;
+
+namespace Luban.Job.Common.Defs
+{
+ public abstract class DefTypeBase
+ {
+ public DefAssemblyBase AssemblyBase { get; set; }
+
+ public int Id { get; protected set; }
+
+ public string TopModule => AssemblyBase.TopModule;
+
+ public IAgent Agent => AssemblyBase.Agent;
+
+ public string Name { get; set; }
+
+ public string Namespace { get; set; }
+
+ public string FullName => TypeUtil.MakeFullName(Namespace, Name);
+
+ public string NamespaceWithTopModule => TypeUtil.MakeNamespace(AssemblyBase.TopModule, Namespace);
+
+ public string FullNameWithTopModule => TypeUtil.MakeFullName(AssemblyBase.TopModule, FullName);
+
+ public string JavaFullName => TypeUtil.MakeFullName(Namespace, Name);
+
+ public string GoFullName => TypeUtil.MakeGoFullName(Namespace, Name);
+
+ public string GoPkgName => TypeUtil.MakeGoPkgName(Namespace);
+
+ public string CppNamespaceBegin => TypeUtil.MakeCppNamespaceBegin(Namespace);
+
+ public string CppNamespaceEnd => TypeUtil.MakeCppNamespaceEnd(Namespace);
+
+ public string CppFullNameWithTopModule => TypeUtil.MakeCppFullName(AssemblyBase.TopModule, FullName);
+
+ public string TypescriptNamespaceBegin => TypeUtil.MakeTypescriptNamespaceBegin(Namespace);
+
+ public string TypescriptNamespaceEnd => TypeUtil.MakeTypescriptNamespaceEnd(Namespace);
+
+ public string CppFullName => TypeUtil.MakeCppFullName(Namespace, Name);
+
+ public string PyFullName => TypeUtil.MakePyFullName(Namespace, Name);
+
+ public string RustFullName => TypeUtil.MakeRustFullName(Namespace, Name);
+
+ public string Comment { get; protected set; }
+
+ public Dictionary Tags { get; protected set; }
+
+ public bool HasTag(string attrName)
+ {
+ return Tags != null && Tags.ContainsKey(attrName);
+ }
+
+ public string GetTag(string attrName)
+ {
+ return Tags != null && Tags.TryGetValue(attrName, out var value) ? value : null;
+ }
+
+ public virtual void PreCompile() { }
+
+ public abstract void Compile();
+
+ public virtual void PostCompile() { }
+ }
+}
diff --git a/src/LubanAssistant/Source/FileInfo.cs b/src/LubanAssistant/Source/FileInfo.cs
new file mode 100644
index 0000000..5493015
--- /dev/null
+++ b/src/LubanAssistant/Source/FileInfo.cs
@@ -0,0 +1,28 @@
+using Bright.Serialization;
+
+namespace Luban.Common.Protos
+{
+ public class FileInfo : BeanBase
+ {
+ public string FilePath { get; set; }
+
+ public string MD5 { get; set; }
+
+ public override void Serialize(ByteBuf os)
+ {
+ os.WriteString(FilePath);
+ os.WriteString(MD5);
+ }
+
+ public override void Deserialize(ByteBuf os)
+ {
+ FilePath = os.ReadString();
+ MD5 = os.ReadString();
+ }
+
+ public override int GetTypeId()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/GetImportFileOrDirectory.cs b/src/LubanAssistant/Source/GetImportFileOrDirectory.cs
new file mode 100644
index 0000000..61f6432
--- /dev/null
+++ b/src/LubanAssistant/Source/GetImportFileOrDirectory.cs
@@ -0,0 +1,63 @@
+using Bright.Serialization;
+using System;
+using System.Collections.Generic;
+
+namespace Luban.Common.Protos
+{
+ public class GetImportFileOrDirectoryArg : BeanBase
+ {
+ public string FileOrDirName { get; set; }
+
+ public List InclusiveSuffixs { get; set; } = new List();
+
+ public override int GetTypeId()
+ {
+ return 0;
+ }
+
+ public override void Serialize(ByteBuf os)
+ {
+ os.WriteString(FileOrDirName);
+ Bright.Common.SerializationUtil.Serialize(os, InclusiveSuffixs);
+ }
+ public override void Deserialize(ByteBuf os)
+ {
+ FileOrDirName = os.ReadString();
+ Bright.Common.SerializationUtil.Deserialize(os, InclusiveSuffixs);
+ }
+ }
+
+ public class GetImportFileOrDirectoryRes : BeanBase
+ {
+ public EErrorCode Err { get; set; }
+
+ public bool IsFile { get; set; }
+
+ public string Md5 { get; set; }
+
+ //public byte[] Content { get; set; }
+
+ public List SubFiles { get; set; }
+
+ public override int GetTypeId()
+ {
+ return 0;
+ }
+
+ public override void Serialize(ByteBuf os)
+ {
+ os.WriteInt((int)Err);
+ os.WriteBool(IsFile);
+ os.WriteString(Md5);
+ Bright.Common.SerializationUtil.Serialize(os, SubFiles);
+ }
+ public override void Deserialize(ByteBuf os)
+ {
+ Err = (EErrorCode)os.ReadInt();
+ IsFile = os.ReadBool();
+ Md5 = os.ReadString();
+ Bright.Common.SerializationUtil.Deserialize(os, SubFiles = new List());
+ }
+ }
+
+}
diff --git a/src/LubanAssistant/Source/LocalAgent.cs b/src/LubanAssistant/Source/LocalAgent.cs
new file mode 100644
index 0000000..4669d28
--- /dev/null
+++ b/src/LubanAssistant/Source/LocalAgent.cs
@@ -0,0 +1,122 @@
+
+using Bright.Time;
+using Luban.Common.Protos;
+using Luban.Common.Utils;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace Luban.Server.Common
+{
+ public class LocalAgent : IAgent
+ {
+ private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
+
+ public LocalAgent()
+ {
+ }
+
+ public Task ReadAllBytesAsync(string file)
+ {
+ return FileUtil.ReadAllBytesAsync(file);
+ }
+
+ public async Task GetFromCacheOrReadAllBytesAsync(string file, string md5)
+ {
+ var content = await ReadAllBytesAsync(file).ConfigureAwait(false);
+ return content;
+ }
+
+
+ public async Task GetFileOrDirectoryAsync(string file, params string[] searchPatterns)
+ {
+ long t1 = TimeUtil.NowMillis;
+ var re = new GetImportFileOrDirectoryRes()
+ {
+ SubFiles = new List(),
+ };
+ var suffixes = new List(searchPatterns.Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s)));
+
+ try
+ {
+ if (Directory.Exists(file))
+ {
+ re.Err = 0;
+ re.IsFile = false;
+ foreach (var subFile in Directory.GetFiles(file, "*", SearchOption.AllDirectories))
+ {
+ if (FileUtil.IsValidInputFile(subFile) && (suffixes.Count == 0 || suffixes.Any(s => subFile.EndsWith(s))))
+ {
+
+ var md5 = FileUtil.CalcMD5(await FileUtil.ReadAllBytesAsync(subFile));
+ re.SubFiles.Add(new Luban.Common.Protos.FileInfo() { FilePath = FileUtil.Standardize(subFile), MD5 = md5 });
+ }
+ }
+
+ }
+ else if (File.Exists(file))
+ {
+ re.IsFile = true;
+ re.Md5 = FileUtil.CalcMD5(await FileUtil.ReadAllBytesAsync(file));
+ }
+ else
+ {
+ re.Err = Luban.Common.EErrorCode.FILE_OR_DIR_NOT_EXISTS;
+ }
+ }
+ catch (Exception e)
+ {
+ re.Err = Luban.Common.EErrorCode.READ_FILE_FAIL;
+ s_logger.Error(e);
+ }
+
+ s_logger.Trace(" GetImportFileOrDirectory file:{file} err:{err} cost:{time}", file, re.Err, TimeUtil.NowMillis - t1);
+
+ return re;
+ }
+
+ public Task QueryFileExistsAsync(QueryFilesExistsArg arg)
+ {
+ var re = new QueryFilesExistsRes() { Exists = new List(arg.Files.Count) };
+ foreach (var f in arg.Files)
+ {
+ re.Exists.Add(File.Exists(Path.Combine(arg.Root, f)));
+ }
+ return Task.FromResult(re);
+ }
+
+ public async Task OpenXmlAsync(string xmlFile)
+ {
+ try
+ {
+ s_logger.Trace("open {xml}", xmlFile);
+ return XElement.Load(new MemoryStream(await ReadAllBytesAsync(xmlFile)));
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"打开定义文件:{xmlFile} 失败 --> {e.Message}");
+ }
+ }
+
+ #region log
+
+ public void Error(string fmt, params object[] objs)
+ {
+ Log("error", string.Format(fmt, objs));
+ }
+
+ public void Info(string fmt, params object[] objs)
+ {
+ Log("info", string.Format(fmt, objs));
+ }
+
+ private void Log(string level, string content)
+ {
+ //Session.Send(new PushLog() { Level = level, LogContent = content });
+ }
+ #endregion
+ }
+}
diff --git a/src/LubanAssistant/Source/QueryFilesExists.cs b/src/LubanAssistant/Source/QueryFilesExists.cs
new file mode 100644
index 0000000..e509c2c
--- /dev/null
+++ b/src/LubanAssistant/Source/QueryFilesExists.cs
@@ -0,0 +1,58 @@
+using Bright.Serialization;
+using System;
+using System.Collections.Generic;
+
+namespace Luban.Common.Protos
+{
+ public class QueryFilesExistsArg : BeanBase
+ {
+ public string Root { get; set; }
+
+ public List Files { get; set; }
+
+ public override int GetTypeId()
+ {
+ return 0;
+ }
+
+ public override void Serialize(ByteBuf os)
+ {
+ os.WriteString(Root);
+ Bright.Common.SerializationUtil.Serialize(os, Files);
+ }
+ public override void Deserialize(ByteBuf os)
+ {
+ Root = os.ReadString();
+ Bright.Common.SerializationUtil.Deserialize(os, Files = new List());
+ }
+ }
+
+ public class QueryFilesExistsRes : BeanBase
+ {
+ public List Exists { get; set; }
+
+ public override int GetTypeId()
+ {
+ return 0;
+ }
+
+ public override void Serialize(ByteBuf os)
+ {
+ os.WriteSize(Exists.Count);
+ foreach (var v in Exists)
+ {
+ os.WriteBool(v);
+ }
+ }
+ public override void Deserialize(ByteBuf os)
+ {
+ int n = os.ReadSize();
+ Exists = new List();
+ for (int i = 0; i < n; i++)
+ {
+ Exists.Add(os.ReadBool());
+ }
+ }
+ }
+
+}
diff --git a/src/LubanAssistant/Source/Serialization/BeanBase.cs b/src/LubanAssistant/Source/Serialization/BeanBase.cs
new file mode 100644
index 0000000..6d56504
--- /dev/null
+++ b/src/LubanAssistant/Source/Serialization/BeanBase.cs
@@ -0,0 +1,11 @@
+namespace Bright.Serialization
+{
+ public abstract class BeanBase : ITypeId, ISerializable
+ {
+ public abstract int GetTypeId();
+
+ public abstract void Serialize(ByteBuf os);
+
+ public abstract void Deserialize(ByteBuf os);
+ }
+}
diff --git a/src/LubanAssistant/Source/Serialization/ByteBuf.cs b/src/LubanAssistant/Source/Serialization/ByteBuf.cs
new file mode 100644
index 0000000..5def2a5
--- /dev/null
+++ b/src/LubanAssistant/Source/Serialization/ByteBuf.cs
@@ -0,0 +1,1687 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Dynamic;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+
+///
+/// TODO
+/// 1. 整理代码
+/// 2. 优化序列化 (像这样 data[endPos + 1] = (byte)(x >> 8) 挨个字节赋值总感觉很低效,能优化吗)
+///
+
+
+namespace Bright.Serialization
+{
+
+ public readonly struct SegmentSaveState
+ {
+ public SegmentSaveState(int readerIndex, int writerIndex)
+ {
+ ReaderIndex = readerIndex;
+ WriterIndex = writerIndex;
+ }
+
+ public int ReaderIndex { get; }
+
+ public int WriterIndex { get; }
+ }
+
+ public sealed class ByteBuf : ICloneable, IEquatable
+ {
+ public ByteBuf()
+ {
+ Bytes = Array.Empty();
+ ReaderIndex = WriterIndex = 0;
+ }
+
+ public ByteBuf(int capacity)
+ {
+ Bytes = capacity > 0 ? new byte[capacity] : Array.Empty();
+ ReaderIndex = 0;
+ WriterIndex = 0;
+ }
+
+ public ByteBuf(byte[] bytes)
+ {
+ Bytes = bytes;
+ ReaderIndex = 0;
+ WriterIndex = Capacity;
+ }
+
+ public ByteBuf(byte[] bytes, int readIndex, int writeIndex)
+ {
+ Bytes = bytes;
+ ReaderIndex = readIndex;
+ WriterIndex = writeIndex;
+ }
+
+ public ByteBuf(int capacity, Action releaser) : this(capacity)
+ {
+ _releaser = releaser;
+ }
+
+ public static ByteBuf Wrap(byte[] bytes)
+ {
+ return new ByteBuf(bytes, 0, bytes.Length);
+ }
+
+ public void Replace(byte[] bytes)
+ {
+ Bytes = bytes;
+ ReaderIndex = 0;
+ WriterIndex = Capacity;
+ }
+
+ public void Replace(byte[] bytes, int beginPos, int endPos)
+ {
+ Bytes = bytes;
+ ReaderIndex = beginPos;
+ WriterIndex = endPos;
+ }
+
+ public int ReaderIndex { get; set; }
+
+ public int WriterIndex { get; set; }
+
+ private readonly Action _releaser;
+
+ public int Capacity => Bytes.Length;
+
+ public int Size { get { return WriterIndex - ReaderIndex; } }
+
+ public bool Empty => WriterIndex <= ReaderIndex;
+
+ public bool NotEmpty => WriterIndex > ReaderIndex;
+
+
+ public void AddWriteIndex(int add)
+ {
+ WriterIndex += add;
+ }
+
+ public void AddReadIndex(int add)
+ {
+ ReaderIndex += add;
+ }
+
+#pragma warning disable CA1819 // 属性不应返回数组
+ public byte[] Bytes { get; private set; }
+#pragma warning restore CA1819 // 属性不应返回数组
+
+ public byte[] CopyData()
+ {
+ var n = Remaining;
+ if (n > 0)
+ {
+ var arr = new byte[n];
+ Buffer.BlockCopy(Bytes, ReaderIndex, arr, 0, n);
+ return arr;
+ }
+ else
+ {
+ return Array.Empty();
+ }
+ }
+
+ public int Remaining { get { return WriterIndex - ReaderIndex; } }
+
+ public void DiscardReadBytes()
+ {
+ WriterIndex -= ReaderIndex;
+ Array.Copy(Bytes, ReaderIndex, Bytes, 0, WriterIndex);
+ ReaderIndex = 0;
+ }
+
+ public int NotCompactWritable { get { return Capacity - WriterIndex; } }
+
+ public void WriteBytesWithoutSize(byte[] bs)
+ {
+ WriteBytesWithoutSize(bs, 0, bs.Length);
+ }
+
+ public void WriteBytesWithoutSize(byte[] bs, int offset, int len)
+ {
+ EnsureWrite(len);
+ Buffer.BlockCopy(bs, offset, Bytes, WriterIndex, len);
+ WriterIndex += len;
+ }
+
+ public void Clear()
+ {
+ ReaderIndex = WriterIndex = 0;
+ }
+
+ private const int MIN_CAPACITY = 16;
+
+ private static int PropSize(int initSize, int needSize)
+ {
+ for (int i = Math.Max(initSize, MIN_CAPACITY); ; i <<= 1)
+ {
+ if (i >= needSize)
+ {
+ return i;
+ }
+ }
+ }
+
+ private void EnsureWrite0(int size)
+ {
+ var needSize = WriterIndex + size - ReaderIndex;
+ if (needSize < Capacity)
+ {
+ WriterIndex -= ReaderIndex;
+ Array.Copy(Bytes, ReaderIndex, Bytes, 0, WriterIndex);
+ ReaderIndex = 0;
+ }
+ else
+ {
+ int newCapacity = PropSize(Capacity, needSize);
+ var newBytes = new byte[newCapacity];
+ WriterIndex -= ReaderIndex;
+ Buffer.BlockCopy(Bytes, ReaderIndex, newBytes, 0, WriterIndex);
+ ReaderIndex = 0;
+ Bytes = newBytes;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void EnsureWrite(int size)
+ {
+ if (WriterIndex + size > Capacity)
+ {
+ EnsureWrite0(size);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void EnsureRead(int size)
+ {
+ if (ReaderIndex + size > WriterIndex)
+ {
+ throw new SerializationException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool CanRead(int size)
+ {
+ return (ReaderIndex + size <= WriterIndex);
+ }
+
+ public void Append(byte x)
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex++] = x;
+ }
+
+ public void WriteBool(bool b)
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex++] = (byte)(b ? 1 : 0);
+ }
+
+ public bool ReadBool()
+ {
+ EnsureRead(1);
+ return Bytes[ReaderIndex++] != 0;
+ }
+
+ public void WriteByte(byte x)
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex++] = x;
+ }
+
+ public byte ReadByte()
+ {
+ EnsureRead(1);
+ return Bytes[ReaderIndex++];
+ }
+
+
+ public void WriteShort(short x)
+ {
+ if (x >= 0)
+ {
+ if (x < 0x80)
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex++] = (byte)x;
+ return;
+ }
+ else if (x < 0x4000)
+ {
+ EnsureWrite(2);
+ Bytes[WriterIndex + 1] = (byte)x;
+ Bytes[WriterIndex] = (byte)((x >> 8) | 0x80);
+ WriterIndex += 2;
+ return;
+ }
+ }
+ EnsureWrite(3);
+ Bytes[WriterIndex] = 0xff;
+ Bytes[WriterIndex + 2] = (byte)x;
+ Bytes[WriterIndex + 1] = (byte)(x >> 8);
+ WriterIndex += 3;
+ }
+
+ public short ReadShort()
+ {
+ EnsureRead(1);
+ int h = Bytes[ReaderIndex];
+ if (h < 0x80)
+ {
+ ReaderIndex++;
+ return (short)h;
+ }
+ else if (h < 0xc0)
+ {
+ EnsureRead(2);
+ int x = ((h & 0x3f) << 8) | Bytes[ReaderIndex + 1];
+ ReaderIndex += 2;
+ return (short)x;
+ }
+ else if ((h == 0xff))
+ {
+ EnsureRead(3);
+ int x = (Bytes[ReaderIndex + 1] << 8) | Bytes[ReaderIndex + 2];
+ ReaderIndex += 3;
+ return (short)x;
+ }
+ else
+ {
+ throw new SerializationException();
+ }
+ }
+
+ public short ReadFshort()
+ {
+ EnsureRead(2);
+ short x;
+#if CPU_SUPPORT_MEMORY_NOT_ALIGN
+ unsafe
+ {
+ fixed (byte* b = &Bytes[ReaderIndex])
+ {
+ x = *(short*)b;
+ }
+ }
+#else
+ x = (short)((Bytes[ReaderIndex + 1] << 8) | Bytes[ReaderIndex]);
+
+#endif
+ ReaderIndex += 2;
+ return x;
+ }
+
+ public void WriteFshort(short x)
+ {
+ EnsureWrite(2);
+#if CPU_SUPPORT_MEMORY_NOT_ALIGN
+ unsafe
+ {
+ fixed (byte* b = &Bytes[WriterIndex])
+ {
+ *(short*)b = x;
+ }
+ }
+#else
+ Bytes[WriterIndex] = (byte)x;
+ Bytes[WriterIndex + 1] = (byte)(x >> 8);
+#endif
+ WriterIndex += 2;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void WriteInt(int x)
+ {
+ WriteUint((uint)x);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int ReadInt()
+ {
+ return (int)ReadUint();
+ }
+
+
+ public void WriteUint(uint x)
+ {
+ // 如果有修改,记得也把 EndWriteSegment改了
+ // 0 111 1111
+ if (x < 0x80)
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex++] = (byte)x;
+ }
+ else if (x < 0x4000) // 10 11 1111, -
+ {
+ EnsureWrite(2);
+ Bytes[WriterIndex + 1] = (byte)x;
+ Bytes[WriterIndex] = (byte)((x >> 8) | 0x80);
+ WriterIndex += 2;
+ }
+ else if (x < 0x200000) // 110 1 1111, -,-
+ {
+ EnsureWrite(3);
+ Bytes[WriterIndex + 2] = (byte)x;
+ Bytes[WriterIndex + 1] = (byte)(x >> 8);
+ Bytes[WriterIndex] = (byte)((x >> 16) | 0xc0);
+ WriterIndex += 3;
+ }
+ else if (x < 0x10000000) // 1110 1111,-,-,-
+ {
+ EnsureWrite(4);
+ Bytes[WriterIndex + 3] = (byte)x;
+ Bytes[WriterIndex + 2] = (byte)(x >> 8);
+ Bytes[WriterIndex + 1] = (byte)(x >> 16);
+ Bytes[WriterIndex] = (byte)((x >> 24) | 0xe0);
+ WriterIndex += 4;
+ }
+ else
+ {
+ EnsureWrite(5);
+ Bytes[WriterIndex] = 0xf0;
+ Bytes[WriterIndex + 4] = (byte)x;
+ Bytes[WriterIndex + 3] = (byte)(x >> 8);
+ Bytes[WriterIndex + 2] = (byte)(x >> 16);
+ Bytes[WriterIndex + 1] = (byte)(x >> 24);
+ WriterIndex += 5;
+ }
+ }
+
+ public uint ReadUint()
+ {
+ ///
+ /// 警告! 如有修改,记得调整 TryDeserializeInplaceOctets
+ EnsureRead(1);
+ uint h = Bytes[ReaderIndex];
+ if (h < 0x80)
+ {
+ ReaderIndex++;
+ return h;
+ }
+ else if (h < 0xc0)
+ {
+ EnsureRead(2);
+ uint x = ((h & 0x3f) << 8) | Bytes[ReaderIndex + 1];
+ ReaderIndex += 2;
+ return x;
+ }
+ else if (h < 0xe0)
+ {
+ EnsureRead(3);
+ uint x = ((h & 0x1f) << 16) | ((uint)Bytes[ReaderIndex + 1] << 8) | Bytes[ReaderIndex + 2];
+ ReaderIndex += 3;
+ return x;
+ }
+ else if (h < 0xf0)
+ {
+
+ EnsureRead(4);
+ uint x = ((h & 0x0f) << 24) | ((uint)Bytes[ReaderIndex + 1] << 16) | ((uint)Bytes[ReaderIndex + 2] << 8) | Bytes[ReaderIndex + 3];
+ ReaderIndex += 4;
+ return x;
+ }
+ else
+ {
+ EnsureRead(5);
+ uint x = ((uint)Bytes[ReaderIndex + 1] << 24) | ((uint)(Bytes[ReaderIndex + 2] << 16)) | ((uint)Bytes[ReaderIndex + 3] << 8) | Bytes[ReaderIndex + 4];
+ ReaderIndex += 5;
+ return x;
+ }
+ }
+
+ public unsafe void WriteUint_Unsafe(uint x)
+ {
+ // 0 111 1111
+ if (x < 0x80)
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex++] = (byte)(x << 1);
+ }
+ else if (x < 0x4000)// 10 11 1111, -
+ {
+ EnsureWrite(2);
+
+ fixed (byte* wb = &Bytes[WriterIndex])
+ {
+ *(uint*)(wb) = (x << 2 | 0b01);
+ }
+
+ WriterIndex += 2;
+ }
+ else if (x < 0x200000) // 110 1 1111, -,-
+ {
+ EnsureWrite(3);
+
+ fixed (byte* wb = &Bytes[WriterIndex])
+ {
+ *(uint*)(wb) = (x << 3 | 0b011);
+ }
+ WriterIndex += 3;
+ }
+ else if (x < 0x10000000) // 1110 1111,-,-,-
+ {
+ EnsureWrite(4);
+ fixed (byte* wb = &Bytes[WriterIndex])
+ {
+ *(uint*)(wb) = (x << 4 | 0b0111);
+ }
+ WriterIndex += 4;
+ }
+ else
+ {
+ EnsureWrite(5);
+ fixed (byte* wb = &Bytes[WriterIndex])
+ {
+ *(uint*)(wb) = (x << 5 | 0b01111);
+ }
+ WriterIndex += 5;
+ }
+ }
+
+ public unsafe uint ReadUint_Unsafe()
+ {
+ ///
+ /// 警告! 如有修改,记得调整 TryDeserializeInplaceOctets
+ EnsureRead(1);
+ uint h = Bytes[ReaderIndex];
+ if ((h & 0b1) == 0b0)
+ {
+ ReaderIndex++;
+ return (h >> 1);
+ }
+ else if ((h & 0b11) == 0b01)
+ {
+ EnsureRead(2);
+ fixed (byte* rb = &Bytes[ReaderIndex])
+ {
+ ReaderIndex += 2;
+ return (*(uint*)rb) >> 2;
+ }
+ }
+ else if ((h & 0b111) == 0b011)
+ {
+ EnsureRead(3);
+ fixed (byte* rb = &Bytes[ReaderIndex])
+ {
+ ReaderIndex += 3;
+ return (*(uint*)rb) >> 3;
+ }
+ }
+ else if ((h & 0b1111) == 0b0111)
+ {
+ EnsureRead(4);
+ fixed (byte* rb = &Bytes[ReaderIndex])
+ {
+ ReaderIndex += 4;
+ return (*(uint*)rb) >> 4;
+ }
+ }
+ else
+ {
+ EnsureRead(5);
+ fixed (byte* rb = &Bytes[ReaderIndex])
+ {
+ ReaderIndex += 5;
+ return (*(uint*)rb) >> 5;
+ }
+ }
+ }
+
+ public int ReadFint()
+ {
+ EnsureRead(4);
+ int x;
+#if CPU_SUPPORT_MEMORY_NOT_ALIGN
+ unsafe
+ {
+ fixed (byte* b = &Bytes[ReaderIndex])
+ {
+ x = *(int*)b;
+ }
+ }
+#else
+ x = (Bytes[ReaderIndex + 3] << 24) | (Bytes[ReaderIndex + 2] << 16) | (Bytes[ReaderIndex + 1] << 8) | (Bytes[ReaderIndex]);
+
+#endif
+ ReaderIndex += 4;
+ return x;
+ }
+
+
+ public void WriteFint(int x)
+ {
+ EnsureWrite(4);
+#if CPU_SUPPORT_MEMORY_NOT_ALIGN
+ unsafe
+ {
+ fixed (byte* b = &Bytes[WriterIndex])
+ {
+ *(int*)b = x;
+ }
+ }
+#else
+ Bytes[WriterIndex] = (byte)x;
+ Bytes[WriterIndex + 1] = (byte)(x >> 8);
+ Bytes[WriterIndex + 2] = (byte)(x >> 16);
+ Bytes[WriterIndex + 3] = (byte)(x >> 24);
+#endif
+ WriterIndex += 4;
+ }
+
+ public int ReadFint_Safe()
+ {
+ EnsureRead(4);
+ int x;
+
+ x = (Bytes[ReaderIndex + 3] << 24) | (Bytes[ReaderIndex + 2] << 16) | (Bytes[ReaderIndex + 1] << 8) | (Bytes[ReaderIndex]);
+
+ ReaderIndex += 4;
+ return x;
+ }
+
+
+ public void WriteFint_Safe(int x)
+ {
+ EnsureWrite(4);
+ Bytes[WriterIndex] = (byte)x;
+ Bytes[WriterIndex + 1] = (byte)(x >> 8);
+ Bytes[WriterIndex + 2] = (byte)(x >> 16);
+ Bytes[WriterIndex + 3] = (byte)(x >> 24);
+ WriterIndex += 4;
+ }
+
+ public void WriteLong(long x)
+ {
+ WriteUlong((ulong)x);
+ }
+
+ public long ReadLong()
+ {
+ return (long)ReadUlong();
+ }
+
+ public void WriteNumberAsLong(double x)
+ {
+ WriteLong((long)x);
+ }
+
+ public double ReadLongAsNumber()
+ {
+ return ReadLong();
+ }
+
+ private void WriteUlong(ulong x)
+ {
+ // 0 111 1111
+ if (x < 0x80)
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex++] = (byte)x;
+ }
+ else if (x < 0x4000) // 10 11 1111, -
+ {
+ EnsureWrite(2);
+ Bytes[WriterIndex + 1] = (byte)x;
+ Bytes[WriterIndex] = (byte)((x >> 8) | 0x80);
+ WriterIndex += 2;
+ }
+ else if (x < 0x200000) // 110 1 1111, -,-
+ {
+ EnsureWrite(3);
+ Bytes[WriterIndex + 2] = (byte)x;
+ Bytes[WriterIndex + 1] = (byte)(x >> 8);
+ Bytes[WriterIndex] = (byte)((x >> 16) | 0xc0);
+ WriterIndex += 3;
+ }
+ else if (x < 0x10000000) // 1110 1111,-,-,-
+ {
+ EnsureWrite(4);
+ Bytes[WriterIndex + 3] = (byte)x;
+ Bytes[WriterIndex + 2] = (byte)(x >> 8);
+ Bytes[WriterIndex + 1] = (byte)(x >> 16);
+ Bytes[WriterIndex] = (byte)((x >> 24) | 0xe0);
+ WriterIndex += 4;
+ }
+ else if (x < 0x800000000L) // 1111 0xxx,-,-,-,-
+ {
+ EnsureWrite(5);
+ Bytes[WriterIndex + 4] = (byte)x;
+ Bytes[WriterIndex + 3] = (byte)(x >> 8);
+ Bytes[WriterIndex + 2] = (byte)(x >> 16);
+ Bytes[WriterIndex + 1] = (byte)(x >> 24);
+ Bytes[WriterIndex] = (byte)((x >> 32) | 0xf0);
+ WriterIndex += 5;
+ }
+ else if (x < 0x40000000000L) // 1111 10xx,
+ {
+ EnsureWrite(6);
+ Bytes[WriterIndex + 5] = (byte)x;
+ Bytes[WriterIndex + 4] = (byte)(x >> 8);
+ Bytes[WriterIndex + 3] = (byte)(x >> 16);
+ Bytes[WriterIndex + 2] = (byte)(x >> 24);
+ Bytes[WriterIndex + 1] = (byte)(x >> 32);
+ Bytes[WriterIndex] = (byte)((x >> 40) | 0xf8);
+ WriterIndex += 6;
+ }
+ else if (x < 0x200000000000L) // 1111 110x,
+ {
+ EnsureWrite(7);
+ Bytes[WriterIndex + 6] = (byte)x;
+ Bytes[WriterIndex + 5] = (byte)(x >> 8);
+ Bytes[WriterIndex + 4] = (byte)(x >> 16);
+ Bytes[WriterIndex + 3] = (byte)(x >> 24);
+ Bytes[WriterIndex + 2] = (byte)(x >> 32);
+ Bytes[WriterIndex + 1] = (byte)(x >> 40);
+ Bytes[WriterIndex] = (byte)((x >> 48) | 0xfc);
+ WriterIndex += 7;
+ }
+ else if (x < 0x100000000000000L) // 1111 1110
+ {
+ EnsureWrite(8);
+ Bytes[WriterIndex + 7] = (byte)x;
+ Bytes[WriterIndex + 6] = (byte)(x >> 8);
+ Bytes[WriterIndex + 5] = (byte)(x >> 16);
+ Bytes[WriterIndex + 4] = (byte)(x >> 24);
+ Bytes[WriterIndex + 3] = (byte)(x >> 32);
+ Bytes[WriterIndex + 2] = (byte)(x >> 40);
+ Bytes[WriterIndex + 1] = (byte)(x >> 48);
+ Bytes[WriterIndex] = 0xfe;
+ WriterIndex += 8;
+ }
+ else // 1111 1111
+ {
+ EnsureWrite(9);
+ Bytes[WriterIndex] = 0xff;
+ Bytes[WriterIndex + 8] = (byte)x;
+ Bytes[WriterIndex + 7] = (byte)(x >> 8);
+ Bytes[WriterIndex + 6] = (byte)(x >> 16);
+ Bytes[WriterIndex + 5] = (byte)(x >> 24);
+ Bytes[WriterIndex + 4] = (byte)(x >> 32);
+ Bytes[WriterIndex + 3] = (byte)(x >> 40);
+ Bytes[WriterIndex + 2] = (byte)(x >> 48);
+ Bytes[WriterIndex + 1] = (byte)(x >> 56);
+ WriterIndex += 9;
+ }
+ }
+
+ public ulong ReadUlong()
+ {
+ EnsureRead(1);
+ uint h = Bytes[ReaderIndex];
+ if (h < 0x80)
+ {
+ ReaderIndex++;
+ return h;
+ }
+ else if (h < 0xc0)
+ {
+ EnsureRead(2);
+ uint x = ((h & 0x3f) << 8) | Bytes[ReaderIndex + 1];
+ ReaderIndex += 2;
+ return x;
+ }
+ else if (h < 0xe0)
+ {
+ EnsureRead(3);
+ uint x = ((h & 0x1f) << 16) | ((uint)Bytes[ReaderIndex + 1] << 8) | Bytes[ReaderIndex + 2];
+ ReaderIndex += 3;
+ return x;
+ }
+ else if (h < 0xf0)
+ {
+ EnsureRead(4);
+ uint x = ((h & 0x0f) << 24) | ((uint)Bytes[ReaderIndex + 1] << 16) | ((uint)Bytes[ReaderIndex + 2] << 8) | Bytes[ReaderIndex + 3];
+ ReaderIndex += 4;
+ return x;
+ }
+ else if (h < 0xf8)
+ {
+ EnsureRead(5);
+ uint xl = ((uint)Bytes[ReaderIndex + 1] << 24) | ((uint)(Bytes[ReaderIndex + 2] << 16)) | ((uint)Bytes[ReaderIndex + 3] << 8) | (Bytes[ReaderIndex + 4]);
+ uint xh = h & 0x07;
+ ReaderIndex += 5;
+ return ((ulong)xh << 32) | xl;
+ }
+ else if (h < 0xfc)
+ {
+ EnsureRead(6);
+ uint xl = ((uint)Bytes[ReaderIndex + 2] << 24) | ((uint)(Bytes[ReaderIndex + 3] << 16)) | ((uint)Bytes[ReaderIndex + 4] << 8) | (Bytes[ReaderIndex + 5]);
+ uint xh = ((h & 0x03) << 8) | Bytes[ReaderIndex + 1];
+ ReaderIndex += 6;
+ return ((ulong)xh << 32) | xl;
+ }
+ else if (h < 0xfe)
+ {
+ EnsureRead(7);
+ uint xl = ((uint)Bytes[ReaderIndex + 3] << 24) | ((uint)(Bytes[ReaderIndex + 4] << 16)) | ((uint)Bytes[ReaderIndex + 5] << 8) | (Bytes[ReaderIndex + 6]);
+ uint xh = ((h & 0x01) << 16) | ((uint)Bytes[ReaderIndex + 1] << 8) | Bytes[ReaderIndex + 2];
+ ReaderIndex += 7;
+ return ((ulong)xh << 32) | xl;
+ }
+ else if (h < 0xff)
+ {
+ EnsureRead(8);
+ uint xl = ((uint)Bytes[ReaderIndex + 4] << 24) | ((uint)(Bytes[ReaderIndex + 5] << 16)) | ((uint)Bytes[ReaderIndex + 6] << 8) | (Bytes[ReaderIndex + 7]);
+ uint xh = /*((h & 0x01) << 24) |*/ ((uint)Bytes[ReaderIndex + 1] << 16) | ((uint)Bytes[ReaderIndex + 2] << 8) | Bytes[ReaderIndex + 3];
+ ReaderIndex += 8;
+ return ((ulong)xh << 32) | xl;
+ }
+ else
+ {
+ EnsureRead(9);
+ uint xl = ((uint)Bytes[ReaderIndex + 5] << 24) | ((uint)(Bytes[ReaderIndex + 6] << 16)) | ((uint)Bytes[ReaderIndex + 7] << 8) | (Bytes[ReaderIndex + 8]);
+ uint xh = ((uint)Bytes[ReaderIndex + 1] << 24) | ((uint)Bytes[ReaderIndex + 2] << 16) | ((uint)Bytes[ReaderIndex + 3] << 8) | Bytes[ReaderIndex + 4];
+ ReaderIndex += 9;
+ return ((ulong)xh << 32) | xl;
+ }
+ }
+
+
+ public void WriteFlong(long x)
+ {
+ EnsureWrite(8);
+#if CPU_SUPPORT_MEMORY_NOT_ALIGN
+ unsafe
+ {
+ fixed (byte* b = &Bytes[WriterIndex])
+ {
+ *(long*)b = x;
+ }
+ }
+#else
+
+ Bytes[WriterIndex] = (byte)x;
+ Bytes[WriterIndex + 1] = (byte)(x >> 8);
+ Bytes[WriterIndex + 2] = (byte)(x >> 16);
+ Bytes[WriterIndex + 3] = (byte)(x >> 24);
+ Bytes[WriterIndex + 4] = (byte)(x >> 32);
+ Bytes[WriterIndex + 5] = (byte)(x >> 40);
+ Bytes[WriterIndex + 6] = (byte)(x >> 48);
+ Bytes[WriterIndex + 7] = (byte)(x >> 56);
+#endif
+ WriterIndex += 8;
+ }
+
+ public long ReadFlong()
+ {
+ EnsureRead(8);
+ long x;
+#if CPU_SUPPORT_MEMORY_NOT_ALIGN
+ unsafe
+ {
+ fixed (byte* b = &Bytes[ReaderIndex])
+ {
+ x = *(long*)b;
+ }
+ }
+#else
+ int xl = (Bytes[ReaderIndex + 3] << 24) | ((Bytes[ReaderIndex + 2] << 16)) | (Bytes[ReaderIndex + 1] << 8) | (Bytes[ReaderIndex]);
+ int xh = (Bytes[ReaderIndex + 7] << 24) | (Bytes[ReaderIndex + 6] << 16) | (Bytes[ReaderIndex + 5] << 8) | Bytes[ReaderIndex + 4];
+ x = ((long)xh << 32) | (long)xl;
+#endif
+ ReaderIndex += 8;
+ return x;
+ }
+
+ private static unsafe void Copy8(byte* dst, byte* src)
+ {
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+ dst[4] = src[4];
+ dst[5] = src[5];
+ dst[6] = src[6];
+ dst[7] = src[7];
+ }
+
+ private static unsafe void Copy4(byte* dst, byte* src)
+ {
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+ }
+
+
+ //const bool isLittleEndian = true;
+ public void WriteFloat(float x)
+ {
+ EnsureWrite(4);
+ unsafe
+ {
+ fixed (byte* b = &Bytes[WriterIndex])
+ {
+#if !CPU_SUPPORT_MEMORY_NOT_ALIGN
+ if ((long)b % 4 == 0)
+ {
+ *(float*)b = x;
+ }
+ else
+ {
+ Copy4(b, (byte*)&x);
+ }
+#else
+ *(float*)b = x;
+#endif
+ }
+ }
+
+ //if (!BitConverter.IsLittleEndian)
+ //{
+ // Array.Reverse(data, endPos, 4);
+ //}
+ WriterIndex += 4;
+ }
+
+ public float ReadFloat()
+ {
+ EnsureRead(4);
+ //if (!BitConverter.IsLittleEndian)
+ //{
+ // Array.Reverse(data, beginPos, 4);
+ //}
+ float x;
+ unsafe
+ {
+ fixed (byte* b = &Bytes[ReaderIndex])
+ {
+#if !CPU_SUPPORT_MEMORY_NOT_ALIGN
+ if ((long)b % 4 == 0)
+ {
+ x = *(float*)b;
+ }
+ else
+ {
+ *((int*)&x) = (b[0]) | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
+ }
+#else
+ x = *(float*)b;
+#endif
+ }
+ }
+
+ ReaderIndex += 4;
+ return x;
+ }
+
+ public void WriteDouble(double x)
+ {
+ EnsureWrite(8);
+ unsafe
+ {
+ fixed (byte* b = &Bytes[WriterIndex])
+ {
+#if !CPU_SUPPORT_MEMORY_NOT_ALIGN
+ if ((long)b % 8 == 0)
+ {
+ *(double*)b = x;
+ }
+ else
+ {
+ Copy8(b, (byte*)&x);
+ }
+#else
+ *(double*)b = x;
+#endif
+ }
+ //if (!BitConverter.IsLittleEndian)
+ //{
+ // Array.Reverse(data, endPos, 8);
+ //}
+ }
+
+ WriterIndex += 8;
+ }
+
+ public double ReadDouble()
+ {
+ EnsureRead(8);
+ //if (!BitConverter.IsLittleEndian)
+ //{
+ // Array.Reverse(data, beginPos, 8);
+ //}
+ double x;
+ unsafe
+ {
+ fixed (byte* b = &Bytes[ReaderIndex])
+ {
+#if !CPU_SUPPORT_MEMORY_NOT_ALIGN
+ if ((long)b % 8 == 0)
+ {
+ x = *(double*)b;
+ }
+ else
+ {
+ int low = (b[0]) | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
+ int high = (b[4]) | (b[5] << 8) | (b[6] << 16) | (b[7] << 24);
+ *((long*)&x) = ((long)high << 32) | (uint)low;
+ }
+#else
+ x = *(double*)b;
+#endif
+ }
+ }
+
+ ReaderIndex += 8;
+ return x;
+ }
+
+ public void WriteSize(int n)
+ {
+ WriteUint((uint)n);
+ }
+
+ public int ReadSize()
+ {
+ return (int)ReadUint();
+ }
+
+ // marshal int
+ // n -> (n << 1) ^ (n >> 31)
+ // Read
+ // (x >>> 1) ^ ((x << 31) >> 31)
+ // (x >>> 1) ^ -(n&1)
+ public void WriteSint(int x)
+ {
+ WriteUint(((uint)x << 1) ^ ((uint)x >> 31));
+ }
+
+ public int ReadSint()
+ {
+ uint x = ReadUint();
+ return (int)((x >> 1) ^ ((x & 1) << 31));
+ }
+
+
+ // marshal long
+ // n -> (n << 1) ^ (n >> 63)
+ // Read
+ // (x >>> 1) ^((x << 63) >> 63)
+ // (x >>> 1) ^ -(n&1L)
+ public void WriteSlong(long x)
+ {
+ WriteUlong(((ulong)x << 1) ^ ((ulong)x >> 63));
+ }
+
+ public long ReadSlong()
+ {
+ long x = ReadLong();
+ return ((long)((ulong)x >> 1) ^ ((x & 1) << 63));
+ }
+
+ public void WriteString(string x)
+ {
+ var n = x != null ? Encoding.UTF8.GetByteCount(x) : 0;
+ WriteSize(n);
+ if (n > 0)
+ {
+ EnsureWrite(n);
+ Encoding.UTF8.GetBytes(x, 0, x.Length, Bytes, WriterIndex);
+ WriterIndex += n;
+ }
+ }
+
+ // byte[], [start, end)
+ public static Func, string> StringCacheFinder { get; set; }
+
+ public string ReadString()
+ {
+ var n = ReadSize();
+ if (n > 0)
+ {
+ EnsureRead(n);
+ string s;
+
+ if (StringCacheFinder == null)
+ {
+ s = Encoding.UTF8.GetString(Bytes, ReaderIndex, n);
+ }
+ else
+ {
+ // 只缓存比较小的字符串
+ s = StringCacheFinder(new ReadOnlyMemory(Bytes, ReaderIndex, n));
+ }
+ ReaderIndex += n;
+ return s;
+ }
+ else
+ {
+ return string.Empty;
+ }
+ }
+
+ public void WriteBytes(byte[] x)
+ {
+ var n = x != null ? x.Length : 0;
+ WriteSize(n);
+ if (n > 0)
+ {
+ EnsureWrite(n);
+ x.CopyTo(Bytes, WriterIndex);
+ WriterIndex += n;
+ }
+ }
+
+ public byte[] ReadBytes()
+ {
+ var n = ReadSize();
+ if (n > 0)
+ {
+ EnsureRead(n);
+ var x = new byte[n];
+ Buffer.BlockCopy(Bytes, ReaderIndex, x, 0, n);
+ ReaderIndex += n;
+ return x;
+ }
+ else
+ {
+ return Array.Empty();
+ }
+ }
+
+ // 以下是一些特殊类型
+
+ public void WriteComplex(Complex x)
+ {
+ WriteDouble(x.Real);
+ WriteDouble(x.Imaginary);
+ }
+
+ public Complex ReadComplex()
+ {
+ var x = ReadDouble();
+ var y = ReadDouble();
+ return new Complex(x, y);
+ }
+
+ public void WriteVector2(Vector2 x)
+ {
+ WriteFloat(x.X);
+ WriteFloat(x.Y);
+ }
+
+ public Vector2 ReadVector2()
+ {
+ float x = ReadFloat();
+ float y = ReadFloat();
+ return new Vector2(x, y);
+ }
+
+ public void WriteVector3(Vector3 x)
+ {
+ WriteFloat(x.X);
+ WriteFloat(x.Y);
+ WriteFloat(x.Z);
+ }
+
+ public Vector3 ReadVector3()
+ {
+ float x = ReadFloat();
+ float y = ReadFloat();
+ float z = ReadFloat();
+ return new Vector3(x, y, z);
+ }
+
+ public void WriteVector4(Vector4 x)
+ {
+ WriteFloat(x.X);
+ WriteFloat(x.Y);
+ WriteFloat(x.Z);
+ WriteFloat(x.W);
+ }
+
+ public Vector4 ReadVector4()
+ {
+ float x = ReadFloat();
+ float y = ReadFloat();
+ float z = ReadFloat();
+ float w = ReadFloat();
+ return new Vector4(x, y, z, w);
+ }
+
+
+ public void WriteQuaternion(Quaternion x)
+ {
+ WriteFloat(x.X);
+ WriteFloat(x.Y);
+ WriteFloat(x.Z);
+ WriteFloat(x.W);
+ }
+
+ public Quaternion ReadQuaternion()
+ {
+ float x = ReadFloat();
+ float y = ReadFloat();
+ float z = ReadFloat();
+ float w = ReadFloat();
+ return new Quaternion(x, y, z, w);
+ }
+
+
+ public void WriteMatrix4x4(Matrix4x4 x)
+ {
+ WriteFloat(x.M11);
+ WriteFloat(x.M12);
+ WriteFloat(x.M13);
+ WriteFloat(x.M14);
+ WriteFloat(x.M21);
+ WriteFloat(x.M22);
+ WriteFloat(x.M23);
+ WriteFloat(x.M24);
+ WriteFloat(x.M31);
+ WriteFloat(x.M32);
+ WriteFloat(x.M33);
+ WriteFloat(x.M34);
+ WriteFloat(x.M41);
+ WriteFloat(x.M42);
+ WriteFloat(x.M43);
+ WriteFloat(x.M44);
+ }
+
+ public Matrix4x4 ReadMatrix4x4()
+ {
+ float m11 = ReadFloat();
+ float m12 = ReadFloat();
+ float m13 = ReadFloat();
+ float m14 = ReadFloat();
+ float m21 = ReadFloat();
+ float m22 = ReadFloat();
+ float m23 = ReadFloat();
+ float m24 = ReadFloat();
+ float m31 = ReadFloat();
+ float m32 = ReadFloat();
+ float m33 = ReadFloat();
+ float m34 = ReadFloat();
+ float m41 = ReadFloat();
+ float m42 = ReadFloat();
+ float m43 = ReadFloat();
+ float m44 = ReadFloat();
+ return new Matrix4x4(m11, m12, m13, m14,
+ m21, m22, m23, m24,
+ m31, m32, m33, m34,
+ m41, m42, m43, m44);
+ }
+
+ public void WriteObjectWithFieldTag(T obj)
+ {
+ switch (obj)
+ {
+ case int i: { WriteByte(FieldTag.INT); WriteInt(i); break; }
+ case long l: { WriteByte(FieldTag.LONG); WriteLong(l); break; }
+ case string s: { WriteByte(FieldTag.STRING); WriteString(s); break; }
+ case bool b: { WriteByte(FieldTag.BOOL); WriteBool(b); break; }
+ case short s2: { WriteByte(FieldTag.SHORT); WriteShort(s2); break; }
+ case float f: { WriteByte(FieldTag.FLOAT); WriteFloat(f); break; }
+ case double d: { WriteByte(FieldTag.DOUBLE); WriteDouble(d); break; }
+ case byte b2: { WriteByte(FieldTag.BYTE); WriteByte(b2); break; }
+ case byte[] b3: { WriteByte(FieldTag.BYTES); WriteBytes(b3); break; }
+ case Vector2 v2: { WriteByte(FieldTag.VECTOR2); WriteVector2(v2); break; }
+ case Vector3 v3: { WriteByte(FieldTag.VECTOR3); WriteVector3(v3); break; }
+ case Vector4 v4: { WriteByte(FieldTag.VECTOR4); WriteVector4(v4); break; }
+ default: throw new SerializationException("unknown object:" + obj);
+ }
+ }
+
+ public object ReadObjectWithFieldTag()
+ {
+ return (ReadByte()) switch
+ {
+ FieldTag.INT => ReadInt(),
+ FieldTag.LONG => ReadLong(),
+ FieldTag.STRING => ReadString(),
+ FieldTag.BOOL => ReadBool(),
+ FieldTag.SHORT => ReadShort(),
+ FieldTag.FLOAT => ReadFloat(),
+ FieldTag.DOUBLE => ReadDouble(),
+ FieldTag.BYTE => ReadByte(),
+ FieldTag.BYTES => ReadBytes(),
+ FieldTag.VECTOR2 => ReadVector2(),
+ FieldTag.VECTOR3 => ReadVector3(),
+ FieldTag.VECTOR4 => ReadVector4(),
+ _ => throw new SerializationException("unknown support field tag"),
+ };
+ }
+
+ internal void SkipBytes()
+ {
+ int n = ReadSize();
+ EnsureRead(n);
+ ReaderIndex += n;
+ }
+
+
+ public void WriteByteBufWithSize(ByteBuf o)
+ {
+ int n = o.Size;
+ if (n > 0)
+ {
+ WriteSize(n);
+ WriteBytesWithoutSize(o.Bytes, o.ReaderIndex, n);
+ }
+ else
+ {
+ WriteByte(0);
+ }
+ }
+
+ public void WriteByteBufWithoutSize(ByteBuf o)
+ {
+ int n = o.Size;
+ if (n > 0)
+ {
+ WriteBytesWithoutSize(o.Bytes, o.ReaderIndex, n);
+ }
+ }
+
+ // 有Segment相关函数后,此函数暂时废弃
+ //public ByteBuf ReadInplaceByteBufWithSize()
+ //{
+ // int n = ReadSize();
+ // EnsureRead(n);
+ // int curReadIndex = ReaderIndex;
+ // ReaderIndex += n;
+ // return new ByteBuf(Bytes, curReadIndex, ReaderIndex);
+ //}
+
+
+ public bool TryReadByte(out byte x)
+ {
+ if (CanRead(1))
+ {
+ x = Bytes[ReaderIndex++];
+ return true;
+ }
+ else
+ {
+ x = 0;
+ return false;
+ }
+ }
+
+ //public EDeserializeError TryDeserialize(Func action)
+ //{
+ // var oldReadIndex = ReaderIndex;
+ // EDeserializeError ret = EDeserializeError.UNMARSHAL_ERR;
+ // try
+ // {
+ // ret = action(this);
+ // return ret;
+ // }
+ // finally
+ // {
+ // if (ret != EDeserializeError.OK)
+ // {
+ // ReaderIndex = oldReadIndex;
+ // }
+ // }
+ //}
+
+ public EDeserializeError TryDeserializeInplaceByteBuf(int maxSize, ByteBuf inplaceTempBody)
+ {
+ //if (!CanRead(1)) { return EDeserializeError.NOT_ENOUGH; }
+ int oldReadIndex = ReaderIndex;
+ bool commit = false;
+ try
+ {
+ int n;
+ int h = Bytes[ReaderIndex];
+ if (h < 0x80)
+ {
+ ReaderIndex++;
+ n = h;
+ }
+ else if (h < 0xc0)
+ {
+ if (!CanRead(2)) { return EDeserializeError.NOT_ENOUGH; }
+ n = ((h & 0x3f) << 8) | Bytes[ReaderIndex + 1];
+ ReaderIndex += 2;
+ }
+ else if (h < 0xe0)
+ {
+ if (!CanRead(3)) { return EDeserializeError.NOT_ENOUGH; }
+ n = ((h & 0x1f) << 16) | (Bytes[ReaderIndex + 1] << 8) | Bytes[ReaderIndex + 2];
+ ReaderIndex += 3;
+ }
+ else if (h < 0xf0)
+ {
+ if (!CanRead(4)) { return EDeserializeError.NOT_ENOUGH; }
+ n = ((h & 0x0f) << 24) | (Bytes[ReaderIndex + 1] << 16) | (Bytes[ReaderIndex + 2] << 8) | Bytes[ReaderIndex + 3];
+ ReaderIndex += 4;
+ }
+ else
+ {
+ return EDeserializeError.EXCEED_SIZE;
+ }
+
+ if (n > maxSize)
+ {
+ return EDeserializeError.EXCEED_SIZE;
+ }
+ if (Remaining < n)
+ {
+ return EDeserializeError.NOT_ENOUGH;
+ }
+
+ int inplaceReadIndex = ReaderIndex;
+ ReaderIndex += n;
+
+ inplaceTempBody.Replace(Bytes, inplaceReadIndex, ReaderIndex);
+ commit = true;
+ }
+ finally
+ {
+ if (!commit)
+ {
+ ReaderIndex = oldReadIndex;
+ }
+ }
+
+ return EDeserializeError.OK;
+ }
+
+
+ // TODO
+ // 优化 未知大小的对象,比如 bean 的序列化
+ public void SkipUnknownField(int tagId)
+ {
+ int tagType = tagId & FieldTag.TAG_MASK;
+ switch (tagType)
+ {
+ case FieldTag.BOOL:
+ ReadBool();
+ break;
+ case FieldTag.BYTE:
+ ReadByte();
+ break;
+ case FieldTag.SHORT:
+ ReadShort();
+ break;
+ case FieldTag.FSHORT:
+ ReadFshort();
+ break;
+ case FieldTag.INT:
+ ReadInt();
+ break;
+ case FieldTag.FINT:
+ ReadFint();
+ break;
+ case FieldTag.LONG:
+ ReadLong();
+ break;
+ case FieldTag.FLONG:
+ ReadFlong();
+ break;
+ case FieldTag.FLOAT:
+ ReadFloat();
+ break;
+ case FieldTag.DOUBLE:
+ ReadDouble();
+ break;
+ case FieldTag.VECTOR2:
+ ReadVector2();
+ break;
+ case FieldTag.VECTOR3:
+ ReadVector3();
+ break;
+ case FieldTag.VECTOR4:
+ ReadVector4();
+ break;
+ case FieldTag.STRING:
+ case FieldTag.BYTES:
+ case FieldTag.ARRAY:
+ case FieldTag.LIST:
+ case FieldTag.SET:
+ case FieldTag.MAP:
+ case FieldTag.BEAN:
+ {
+ SkipBytes();
+ break;
+ }
+ case FieldTag.DYNAMIC_BEAN:
+ {
+ int type = ReadInt();
+ if (type != 0)
+ {
+ SkipBytes();
+ }
+ break;
+ }
+ default:
+ throw new SerializationException();
+ }
+ }
+
+ public void WriteRawTag(byte b1)
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex++] = b1;
+ }
+
+ public void WriteRawTag(byte b1, byte b2)
+ {
+ EnsureWrite(2);
+ Bytes[WriterIndex] = b1;
+ Bytes[WriterIndex + 1] = b2;
+ WriterIndex += 2;
+ }
+
+ public void WriteRawTag(byte b1, byte b2, byte b3)
+ {
+ EnsureWrite(3);
+ Bytes[WriterIndex] = b1;
+ Bytes[WriterIndex + 1] = b2;
+ Bytes[WriterIndex + 2] = b3;
+ WriterIndex += 3;
+ }
+
+ #region segment
+
+
+ public void BeginWriteSegment(out int oldSize)
+ {
+ oldSize = Size;
+ EnsureWrite(1);
+ WriterIndex += 1;
+ }
+
+ public void EndWriteSegment(int oldSize)
+ {
+ int startPos = ReaderIndex + oldSize;
+ int segmentSize = WriterIndex - startPos - 1;
+
+ // 0 111 1111
+ if (segmentSize < 0x80)
+ {
+ Bytes[startPos] = (byte)segmentSize;
+ }
+ else if (segmentSize < 0x4000) // 10 11 1111, -
+ {
+ EnsureWrite(1);
+ Bytes[WriterIndex] = Bytes[startPos + 1];
+ Bytes[startPos + 1] = (byte)segmentSize;
+
+ Bytes[startPos] = (byte)((segmentSize >> 8) | 0x80);
+ WriterIndex += 1;
+ }
+ else if (segmentSize < 0x200000) // 110 1 1111, -,-
+ {
+ EnsureWrite(2);
+ Bytes[WriterIndex + 1] = Bytes[startPos + 2];
+ Bytes[startPos + 2] = (byte)segmentSize;
+
+ Bytes[WriterIndex] = Bytes[startPos + 1];
+ Bytes[startPos + 1] = (byte)(segmentSize >> 8);
+
+ Bytes[startPos] = (byte)((segmentSize >> 16) | 0xc0);
+ WriterIndex += 2;
+ }
+ else if (segmentSize < 0x10000000) // 1110 1111,-,-,-
+ {
+ EnsureWrite(3);
+ Bytes[WriterIndex + 2] = Bytes[startPos + 3];
+ Bytes[startPos + 3] = (byte)segmentSize;
+
+ Bytes[WriterIndex + 1] = Bytes[startPos + 2];
+ Bytes[startPos + 2] = (byte)(segmentSize >> 8);
+
+ Bytes[WriterIndex] = Bytes[startPos + 1];
+ Bytes[startPos + 1] = (byte)(segmentSize >> 16);
+
+ Bytes[startPos] = (byte)((segmentSize >> 24) | 0xe0);
+ WriterIndex += 3;
+ }
+ else
+ {
+ throw new SerializationException("exceed max segment size");
+ }
+ }
+
+ public void ReadSegment(out int startIndex, out int segmentSize)
+ {
+ EnsureRead(1);
+ int h = Bytes[ReaderIndex++];
+
+ startIndex = ReaderIndex;
+
+ if (h < 0x80)
+ {
+ segmentSize = h;
+ ReaderIndex += segmentSize;
+ }
+ else if (h < 0xc0)
+ {
+ EnsureRead(1);
+ segmentSize = ((h & 0x3f) << 8) | Bytes[ReaderIndex];
+ int endPos = ReaderIndex + segmentSize;
+ Bytes[ReaderIndex] = Bytes[endPos];
+ ReaderIndex += segmentSize + 1;
+ }
+ else if (h < 0xe0)
+ {
+ EnsureRead(2);
+ segmentSize = ((h & 0x1f) << 16) | ((int)Bytes[ReaderIndex] << 8) | Bytes[ReaderIndex + 1];
+ int endPos = ReaderIndex + segmentSize;
+ Bytes[ReaderIndex] = Bytes[endPos];
+ Bytes[ReaderIndex + 1] = Bytes[endPos + 1];
+ ReaderIndex += segmentSize + 2;
+ }
+ else if (h < 0xf0)
+ {
+ EnsureRead(3);
+ segmentSize = ((h & 0x0f) << 24) | ((int)Bytes[ReaderIndex] << 16) | ((int)Bytes[ReaderIndex + 1] << 8) | Bytes[ReaderIndex + 2];
+ int endPos = ReaderIndex + segmentSize;
+ Bytes[ReaderIndex] = Bytes[endPos];
+ Bytes[ReaderIndex + 1] = Bytes[endPos + 1];
+ Bytes[ReaderIndex + 2] = Bytes[endPos + 2];
+ ReaderIndex += segmentSize + 3;
+ }
+ else
+ {
+ throw new SerializationException("exceed max size");
+ }
+ if (ReaderIndex > WriterIndex)
+ {
+ throw new SerializationException("segment data not enough");
+ }
+ }
+
+ public void ReadSegment(ByteBuf buf)
+ {
+ ReadSegment(out int startPos, out var size);
+ buf.Bytes = Bytes;
+ buf.ReaderIndex = startPos;
+ buf.WriterIndex = startPos + size;
+ }
+
+ public void EnterSegment(out SegmentSaveState saveState)
+ {
+ ReadSegment(out int startPos, out int size);
+
+ saveState = new SegmentSaveState(ReaderIndex, WriterIndex);
+ ReaderIndex = startPos;
+ WriterIndex = startPos + size;
+ }
+
+ public void LeaveSegment(SegmentSaveState saveState)
+ {
+ ReaderIndex = saveState.ReaderIndex;
+ WriterIndex = saveState.WriterIndex;
+ }
+
+ #endregion
+
+ public override string ToString()
+ {
+ string[] datas = new string[WriterIndex - ReaderIndex];
+ for (var i = ReaderIndex; i < WriterIndex; i++)
+ {
+ datas[i - ReaderIndex] = Bytes[i].ToString("X2");
+ }
+ return string.Join(".", datas);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return (obj is ByteBuf other) && Equals(other);
+ }
+
+ public bool Equals(ByteBuf other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+ if (Size != other.Size)
+ {
+ return false;
+ }
+ for (int i = 0, n = Size; i < n; i++)
+ {
+ if (Bytes[ReaderIndex + i] != other.Bytes[other.ReaderIndex + i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public object Clone()
+ {
+ return new ByteBuf(CopyData());
+ }
+
+
+ public static ByteBuf FromString(string value)
+ {
+ var ss = value.Split(',');
+ byte[] data = new byte[ss.Length];
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i] = byte.Parse(ss[i]);
+ }
+ return new ByteBuf(data);
+ }
+
+ public override int GetHashCode()
+ {
+ int hash = 17;
+ for (int i = ReaderIndex; i < WriterIndex; i++)
+ {
+ hash = hash * 23 + Bytes[i];
+ }
+ return hash;
+ }
+
+ public void Release()
+ {
+ _releaser?.Invoke(this);
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Serialization/EUnmarshalError.cs b/src/LubanAssistant/Source/Serialization/EUnmarshalError.cs
new file mode 100644
index 0000000..1293c51
--- /dev/null
+++ b/src/LubanAssistant/Source/Serialization/EUnmarshalError.cs
@@ -0,0 +1,10 @@
+namespace Bright.Serialization
+{
+ public enum EDeserializeError
+ {
+ OK,
+ NOT_ENOUGH,
+ EXCEED_SIZE,
+ // UNMARSHAL_ERR,
+ }
+}
diff --git a/src/LubanAssistant/Source/Serialization/FieldTag.cs b/src/LubanAssistant/Source/Serialization/FieldTag.cs
new file mode 100644
index 0000000..ff67c6c
--- /dev/null
+++ b/src/LubanAssistant/Source/Serialization/FieldTag.cs
@@ -0,0 +1,42 @@
+namespace Bright.Serialization
+{
+
+ // 把 int,long,string,bool 调整到最小
+ // 这样 marshal compatible write(field_id << tag_shift | tag_id) < 2^7 能在一个字节
+ // 内序列化, 优化序列化最终大小
+#pragma warning disable CA1720 // 标识符包含类型名称
+ public static class FieldTag
+ {
+ public const int
+ INT = 0,
+ LONG = 1,
+ STRING = 2,
+ BOOL = 3,
+
+ BYTE = 4,
+ SHORT = 5,
+ FSHORT = 6,
+ FINT = 7,
+ FLONG = 8,
+ FLOAT = 9,
+ DOUBLE = 10,
+ BYTES = 11,
+ ARRAY = 12,
+ LIST = 13,
+ SET = 14,
+ MAP = 15,
+ BEAN = 16,
+ TEXT = 17,
+ VECTOR2 = 18,
+ VECTOR3 = 19,
+ VECTOR4 = 20,
+ DYNAMIC_BEAN = 21,
+
+ NOT_USE = 22;
+
+
+ public const int TAG_SHIFT = 5;
+ public const int TAG_MASK = (1 << TAG_SHIFT) - 1;
+ }
+#pragma warning restore CA1720 // 标识符包含类型名称
+}
diff --git a/src/LubanAssistant/Source/Serialization/ISerializable.cs b/src/LubanAssistant/Source/Serialization/ISerializable.cs
new file mode 100644
index 0000000..b0eff60
--- /dev/null
+++ b/src/LubanAssistant/Source/Serialization/ISerializable.cs
@@ -0,0 +1,13 @@
+namespace Bright.Serialization
+{
+ ///
+ /// 非兼容binary序列化
+ ///
+ public interface ISerializable
+ {
+ void Serialize(ByteBuf os);
+
+ void Deserialize(ByteBuf os);
+ }
+
+}
diff --git a/src/LubanAssistant/Source/Serialization/ITypeId.cs b/src/LubanAssistant/Source/Serialization/ITypeId.cs
new file mode 100644
index 0000000..2b8d71a
--- /dev/null
+++ b/src/LubanAssistant/Source/Serialization/ITypeId.cs
@@ -0,0 +1,7 @@
+namespace Bright.Serialization
+{
+ public interface ITypeId
+ {
+ int GetTypeId();
+ }
+}
diff --git a/src/LubanAssistant/Source/Serialization/SerializationException.cs b/src/LubanAssistant/Source/Serialization/SerializationException.cs
new file mode 100644
index 0000000..292f00e
--- /dev/null
+++ b/src/LubanAssistant/Source/Serialization/SerializationException.cs
@@ -0,0 +1,24 @@
+#define CPU_SUPPORT_MEMORY_NOT_ALIGN //CPU 是否支持读取非对齐内存
+
+using System;
+
+
+///
+/// TODO
+/// 1. 整理代码
+/// 2. 优化序列化 (像这样 data[endPos + 1] = (byte)(x >> 8) 挨个字节赋值总感觉很低效,能优化吗)
+///
+
+
+namespace Bright.Serialization
+{
+ public class SerializationException : Exception
+ {
+ public SerializationException() { }
+ public SerializationException(string msg) : base(msg) { }
+
+ public SerializationException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/src/LubanAssistant/Source/Utils/DataLoaderUtil.cs b/src/LubanAssistant/Source/Utils/DataLoaderUtil.cs
new file mode 100644
index 0000000..a61dfea
--- /dev/null
+++ b/src/LubanAssistant/Source/Utils/DataLoaderUtil.cs
@@ -0,0 +1,223 @@
+using Bright.Time;
+using Luban.Common.Utils;
+using Luban.Job.Cfg.DataCreators;
+using Luban.Job.Cfg.Datas;
+using Luban.Job.Cfg.DataSources;
+using Luban.Job.Cfg.Defs;
+using Luban.Job.Common.Types;
+using Luban.Job.Common.Utils;
+using Luban.Server.Common;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Luban.Job.Cfg.Utils
+{
+ public static class DataLoaderUtil
+ {
+ private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
+
+ public class InputFileInfo
+ {
+ public string MD5 { get; set; }
+
+ public string OriginFile { get; set; }
+
+ public string ActualFile { get; set; }
+
+ public string SheetName { get; set; }
+ }
+
+ public static async Task> CollectInputFilesAsync(IAgent agent, IEnumerable files, string dataDir)
+ {
+ var collectTasks = new List>>();
+ foreach (var file in files)
+ {
+ (var actualFile, var sheetName) = FileUtil.SplitFileAndSheetName(FileUtil.Standardize(file));
+ var actualFullPath = FileUtil.Combine(dataDir, actualFile);
+ var originFullPath = FileUtil.Combine(dataDir, file);
+ //s_logger.Info("== get input file:{file} actualFile:{actual}", file, actualFile);
+
+ collectTasks.Add(Task.Run(async () =>
+ {
+ var fileOrDirContent = await agent.GetFileOrDirectoryAsync(actualFullPath, DataSourceFactory.validDataSourceSuffixes);
+ if (fileOrDirContent.IsFile)
+ {
+ return new List { new InputFileInfo() { OriginFile = file, ActualFile = actualFullPath, SheetName = sheetName, MD5 = fileOrDirContent.Md5 } };
+ }
+ else
+ {
+ return fileOrDirContent.SubFiles.Select(f => new InputFileInfo() { OriginFile = f.FilePath, ActualFile = f.FilePath, MD5 = f.MD5 }).ToList();
+ }
+ }));
+ }
+
+ var allFiles = new List();
+ foreach (var t in collectTasks)
+ {
+ allFiles.AddRange(await t);
+ }
+ return allFiles;
+ }
+
+ //private async Task> CollectInputFilesAsync(RemoteAgent agent, DefTable table, string dataDir)
+ //{
+ // var collectTasks = new List>>();
+ // foreach (var file in table.InputFiles)
+ // return CollectInputFilesAsync(agent, table.InputFiles, dataDir)
+ //}
+
+ public static async Task GenerateLoadRecordFromFileTasksAsync(IAgent agent, DefTable table, string dataDir, List inputFiles2, List>> tasks)
+ {
+ var inputFileInfos = await CollectInputFilesAsync(agent, inputFiles2, dataDir);
+
+ // check cache (table, exporttestdata) -> (list, List)
+ // (md5, sheetName,exportTestData) -> (value_type, List)
+
+ foreach (var file in inputFileInfos)
+ {
+ var actualFile = file.ActualFile;
+ //s_logger.Info("== get input file:{file} actualFile:{actual}", file, actualFile);
+
+ tasks.Add(Task.Run(async () =>
+ {
+ var res = LoadCfgRecords(table.ValueTType,
+ file.OriginFile,
+ file.SheetName,
+ await agent.GetFromCacheOrReadAllBytesAsync(file.ActualFile, file.MD5),
+ FileUtil.IsExcelFile(file.ActualFile));
+
+ return res;
+ }));
+ }
+ }
+
+ public static async Task LoadTableAsync(IAgent agent, DefTable table, string dataDir, string patchName, string patchDataDir)
+ {
+ var mainLoadTasks = new List>>();
+ var mainGenerateTask = GenerateLoadRecordFromFileTasksAsync(agent, table, dataDir, table.InputFiles, mainLoadTasks);
+
+ var patchLoadTasks = new List>>();
+
+ Task patchGenerateTask = null;
+ if (!string.IsNullOrWhiteSpace(patchName))
+ {
+ var patchInputFiles = table.GetPatchInputFiles(patchName);
+ if (patchInputFiles != null)
+ {
+ patchGenerateTask = GenerateLoadRecordFromFileTasksAsync(agent, table, patchDataDir, patchInputFiles, patchLoadTasks);
+ }
+ }
+
+ await mainGenerateTask;
+
+ var mainRecords = new List(256);
+ foreach (var task in mainLoadTasks)
+ {
+ mainRecords.AddRange(await task);
+ }
+ s_logger.Trace("== load main records. count:{count}", mainRecords.Count);
+
+ List patchRecords = null;
+ if (patchGenerateTask != null)
+ {
+ patchRecords = new List(64);
+ await patchGenerateTask;
+ foreach (var task in patchLoadTasks)
+ {
+ patchRecords.AddRange(await task);
+ }
+ s_logger.Trace("== load patch records. count:{count}", patchRecords.Count);
+ }
+
+ table.Assembly.AddDataTable(table, mainRecords, patchRecords);
+
+ s_logger.Trace("table:{name} record num:{num}", table.FullName, mainRecords.Count);
+ }
+
+ public static async Task LoadCfgDataAsync(IAgent agent, DefAssembly ass, string dataDir, string patchName, string patchDataDir)
+ {
+ var ctx = agent;
+ List exportTables = ass.Types.Values.Where(t => t is DefTable ct && ct.NeedExport).Select(t => (DefTable)t).ToList();
+ var genDataTasks = new List();
+ var outputDataFiles = new ConcurrentBag();
+ long genDataStartTime = TimeUtil.NowMillis;
+
+ foreach (DefTable c in exportTables)
+ {
+ var table = c;
+ genDataTasks.Add(Task.Run(async () =>
+ {
+ long beginTime = TimeUtil.NowMillis;
+ await LoadTableAsync(agent, table, dataDir, patchName, patchDataDir);
+ long endTime = TimeUtil.NowMillis;
+ if (endTime - beginTime > 100)
+ {
+ ctx.Info("====== load {0} cost {1} ms ======", table.FullName, (endTime - beginTime));
+ }
+ }));
+ }
+ await Task.WhenAll(genDataTasks.ToArray());
+ }
+
+ public static List LoadCfgRecords(TBean recordType, string originFile, string sheetName, byte[] content, bool multiRecord)
+ {
+ // (md5,sheet,multiRecord,exportTestData) -> (valuetype, List<(datas)>)
+ var dataSource = DataSourceFactory.Create(originFile, sheetName, new MemoryStream(content));
+ try
+ {
+ if (multiRecord)
+ {
+ return dataSource.ReadMulti(recordType);
+ }
+ else
+ {
+ Record record = dataSource.ReadOne(recordType);
+ return record != null ? new List { record } : new List();
+ }
+ }
+ catch (DataCreateException dce)
+ {
+ if (string.IsNullOrWhiteSpace(dce.OriginDataLocation))
+ {
+ dce.OriginDataLocation = originFile;
+ }
+ throw;
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"配置文件:{originFile} 生成失败.", e);
+ }
+ }
+
+#if !LUBAN_ASSISTANT
+ public static async Task LoadTextTablesAsync(IAgent agent, DefAssembly ass, string baseDir, string textTableFiles)
+ {
+ var tasks = new List>();
+ var files = textTableFiles.Split(',');
+ foreach (var file in await CollectInputFilesAsync(agent, files, baseDir))
+ {
+ tasks.Add(agent.GetFromCacheOrReadAllBytesAsync(file.ActualFile, file.MD5));
+ }
+
+ var textTable = ass.ExportTextTable;
+ for (int i = 0; i < tasks.Count; i++)
+ {
+ var bytes = await tasks[i];
+ try
+ {
+ textTable.LoadFromFile(files[i], bytes);
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"load text table file:{files[i]} fail", e);
+ }
+ }
+ }
+#endif
+
+ }
+}
diff --git a/src/LubanAssistant/ThisAddIn.cs b/src/LubanAssistant/ThisAddIn.cs
index 9aa8a08..c688b8a 100644
--- a/src/LubanAssistant/ThisAddIn.cs
+++ b/src/LubanAssistant/ThisAddIn.cs
@@ -6,6 +6,7 @@ using System.Xml.Linq;
using Excel = Microsoft.Office.Interop.Excel;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Excel;
+using Microsoft.Office.Tools.Ribbon;
namespace LubanAssistant
{
@@ -19,6 +20,17 @@ namespace LubanAssistant
{
}
+ //protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
+ //{
+ // return new ToolTab();
+ //}
+
+
+ protected override IRibbonExtension[] CreateRibbonObjects()
+ {
+ return new IRibbonExtension[] { new AssistantTab() };
+ }
+
#region VSTO 生成的代码
///
@@ -30,7 +42,7 @@ namespace LubanAssistant
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
-
+
#endregion
}
}
diff --git a/src/LubanAssistant/app.config b/src/LubanAssistant/app.config
new file mode 100644
index 0000000..fc8bbbf
--- /dev/null
+++ b/src/LubanAssistant/app.config
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/LubanAssistant/packages.config b/src/LubanAssistant/packages.config
new file mode 100644
index 0000000..27dbb33
--- /dev/null
+++ b/src/LubanAssistant/packages.config
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file