//------------------------------------------------------------ // Game Framework // Copyright © 2013-2021 Jiang Yin. All rights reserved. // Homepage: https://gameframework.cn/ // Feedback: mailto:ellan@gameframework.cn //------------------------------------------------------------ using GameFramework; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEngine; namespace MetaClient.Editor.DataTableTools { public sealed partial class DataTableProcessor { private const string CommentLineSeparator = "#"; private static readonly char[] DataSplitSeparators = new char[] { '\t' }; private static readonly char[] DataTrimSeparators = new char[] { '\"' }; private readonly string[] m_NameRow; private readonly string[] m_TypeRow; private readonly string[] m_DefaultValueRow; private readonly string[] m_CommentRow; private readonly int m_ContentStartRow; private readonly int m_IdColumn; private readonly DataProcessor[] m_DataProcessor; private readonly string[][] m_RawValues; private readonly string[] m_Strings; private string m_CodeTemplate; private DataTableCodeGenerator m_CodeGenerator; public DataTableProcessor(string dataTableFileName, Encoding encoding, int nameRow, int typeRow, int? defaultValueRow, int? commentRow, int contentStartRow, int idColumn) { if (string.IsNullOrEmpty(dataTableFileName)) { throw new GameFrameworkException("Data table file name is invalid."); } if (!dataTableFileName.EndsWith(".txt", StringComparison.Ordinal)) { throw new GameFrameworkException(Utility.Text.Format("Data table file '{0}' is not a txt.", dataTableFileName)); } if (!File.Exists(dataTableFileName)) { throw new GameFrameworkException(Utility.Text.Format("Data table file '{0}' is not exist.", dataTableFileName)); } string[] lines = File.ReadAllLines(dataTableFileName, encoding); int rawRowCount = lines.Length; int rawColumnCount = 0; List rawValues = new List(); for (int i = 0; i < lines.Length; i++) { string[] rawValue = lines[i].Split(DataSplitSeparators); for (int j = 0; j < rawValue.Length; j++) { rawValue[j] = rawValue[j].Trim(DataTrimSeparators); } if (i == 0) { rawColumnCount = rawValue.Length; } else if (rawValue.Length != rawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Data table file '{0}', raw Column is '{2}', but line '{1}' column is '{3}'.", dataTableFileName, i, rawColumnCount, rawValue.Length)); } rawValues.Add(rawValue); } m_RawValues = rawValues.ToArray(); if (nameRow < 0) { throw new GameFrameworkException(Utility.Text.Format("Name row '{0}' is invalid.", nameRow)); } if (typeRow < 0) { throw new GameFrameworkException(Utility.Text.Format("Type row '{0}' is invalid.", typeRow)); } if (contentStartRow < 0) { throw new GameFrameworkException(Utility.Text.Format("Content start row '{0}' is invalid.", contentStartRow)); } if (idColumn < 0) { throw new GameFrameworkException(Utility.Text.Format("Id column '{0}' is invalid.", idColumn)); } if (nameRow >= rawRowCount) { throw new GameFrameworkException(Utility.Text.Format("Name row '{0}' >= raw row count '{1}' is not allow.", nameRow, rawRowCount)); } if (typeRow >= rawRowCount) { throw new GameFrameworkException(Utility.Text.Format("Type row '{0}' >= raw row count '{1}' is not allow.", typeRow, rawRowCount)); } if (defaultValueRow.HasValue && defaultValueRow.Value >= rawRowCount) { throw new GameFrameworkException(Utility.Text.Format("Default value row '{0}' >= raw row count '{1}' is not allow.", defaultValueRow.Value, rawRowCount)); } if (commentRow.HasValue && commentRow.Value >= rawRowCount) { throw new GameFrameworkException(Utility.Text.Format("Comment row '{0}' >= raw row count '{1}' is not allow.", commentRow.Value, rawRowCount)); } if (contentStartRow > rawRowCount) { throw new GameFrameworkException(Utility.Text.Format("Content start row '{0}' > raw row count '{1}' is not allow.", contentStartRow, rawRowCount)); } if (idColumn >= rawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Id column '{0}' >= raw column count '{1}' is not allow.", idColumn, rawColumnCount)); } m_NameRow = m_RawValues[nameRow]; m_TypeRow = m_RawValues[typeRow]; m_DefaultValueRow = defaultValueRow.HasValue ? m_RawValues[defaultValueRow.Value] : null; m_CommentRow = commentRow.HasValue ? m_RawValues[commentRow.Value] : null; m_ContentStartRow = contentStartRow; m_IdColumn = idColumn; m_DataProcessor = new DataProcessor[rawColumnCount]; for (int i = 0; i < rawColumnCount; i++) { if (i == IdColumn) { m_DataProcessor[i] = DataProcessorUtility.GetDataProcessor("id"); } else { m_DataProcessor[i] = DataProcessorUtility.GetDataProcessor(m_TypeRow[i]); } } Dictionary strings = new Dictionary(StringComparer.Ordinal); for (int i = contentStartRow; i < rawRowCount; i++) { if (IsCommentRow(i)) { continue; } for (int j = 0; j < rawColumnCount; j++) { if (m_DataProcessor[j].LanguageKeyword != "string") { continue; } string str = m_RawValues[i][j]; if (strings.ContainsKey(str)) { strings[str]++; } else { strings[str] = 1; } } } m_Strings = strings.OrderBy(value => value.Key).OrderByDescending(value => value.Value).Select(value => value.Key).ToArray(); m_CodeTemplate = null; m_CodeGenerator = null; } public int RawRowCount { get { return m_RawValues.Length; } } public int RawColumnCount { get { return m_RawValues.Length > 0 ? m_RawValues[0].Length : 0; } } public int StringCount { get { return m_Strings.Length; } } public int ContentStartRow { get { return m_ContentStartRow; } } public int IdColumn { get { return m_IdColumn; } } public bool IsIdColumn(int rawColumn) { if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } return m_DataProcessor[rawColumn].IsId; } public bool IsCommentRow(int rawRow) { if (rawRow < 0 || rawRow >= RawRowCount) { throw new GameFrameworkException(Utility.Text.Format("Raw row '{0}' is out of range.", rawRow)); } return GetValue(rawRow, 0).StartsWith(CommentLineSeparator, StringComparison.Ordinal); } public bool IsCommentColumn(int rawColumn) { if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } return string.IsNullOrEmpty(GetName(rawColumn)) || m_DataProcessor[rawColumn].IsComment; } public string GetName(int rawColumn) { if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } if (IsIdColumn(rawColumn)) { return "Id"; } return m_NameRow[rawColumn]; } public bool IsSystem(int rawColumn) { if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } return m_DataProcessor[rawColumn].IsSystem; } public System.Type GetType(int rawColumn) { if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } return m_DataProcessor[rawColumn].Type; } public string GetLanguageKeyword(int rawColumn) { if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } return m_DataProcessor[rawColumn].LanguageKeyword; } public string GetDefaultValue(int rawColumn) { if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } return m_DefaultValueRow != null ? m_DefaultValueRow[rawColumn] : null; } public string GetComment(int rawColumn) { if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } return m_CommentRow != null ? m_CommentRow[rawColumn] : null; } public string GetValue(int rawRow, int rawColumn) { if (rawRow < 0 || rawRow >= RawRowCount) { throw new GameFrameworkException(Utility.Text.Format("Raw row '{0}' is out of range.", rawRow)); } if (rawColumn < 0 || rawColumn >= RawColumnCount) { throw new GameFrameworkException(Utility.Text.Format("Raw column '{0}' is out of range.", rawColumn)); } return m_RawValues[rawRow][rawColumn]; } public string GetString(int index) { if (index < 0 || index >= StringCount) { throw new GameFrameworkException(Utility.Text.Format("String index '{0}' is out of range.", index)); } return m_Strings[index]; } public int GetStringIndex(string str) { for (int i = 0; i < StringCount; i++) { if (m_Strings[i] == str) { return i; } } return -1; } public bool GenerateDataFile(string outputFileName) { if (string.IsNullOrEmpty(outputFileName)) { throw new GameFrameworkException("Output file name is invalid."); } try { using (FileStream fileStream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write)) { using (BinaryWriter binaryWriter = new BinaryWriter(fileStream, Encoding.UTF8)) { for (int rawRow = ContentStartRow; rawRow < RawRowCount; rawRow++) { if (IsCommentRow(rawRow)) { continue; } byte[] bytes = GetRowBytes(outputFileName, rawRow); binaryWriter.Write7BitEncodedInt32(bytes.Length); binaryWriter.Write(bytes); } } } Debug.Log(Utility.Text.Format("Parse data table '{0}' success.", outputFileName)); return true; } catch (Exception exception) { Debug.LogError(Utility.Text.Format("Parse data table '{0}' failure, exception is '{1}'.", outputFileName, exception)); return false; } } public bool SetCodeTemplate(string codeTemplateFileName, Encoding encoding) { try { m_CodeTemplate = File.ReadAllText(codeTemplateFileName, encoding); Debug.Log(Utility.Text.Format("Set code template '{0}' success.", codeTemplateFileName)); return true; } catch (Exception exception) { Debug.LogError(Utility.Text.Format("Set code template '{0}' failure, exception is '{1}'.", codeTemplateFileName, exception)); return false; } } public void SetCodeGenerator(DataTableCodeGenerator codeGenerator) { m_CodeGenerator = codeGenerator; } public bool GenerateCodeFile(string outputFileName, Encoding encoding, object userData = null) { if (string.IsNullOrEmpty(m_CodeTemplate)) { throw new GameFrameworkException("You must set code template first."); } if (string.IsNullOrEmpty(outputFileName)) { throw new GameFrameworkException("Output file name is invalid."); } try { StringBuilder stringBuilder = new StringBuilder(m_CodeTemplate); if (m_CodeGenerator != null) { m_CodeGenerator(this, stringBuilder, userData); } using (FileStream fileStream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write)) { using (StreamWriter stream = new StreamWriter(fileStream, encoding)) { stream.Write(stringBuilder.ToString()); } } Debug.Log(Utility.Text.Format("Generate code file '{0}' success.", outputFileName)); return true; } catch (Exception exception) { Debug.LogError(Utility.Text.Format("Generate code file '{0}' failure, exception is '{1}'.", outputFileName, exception)); return false; } } private byte[] GetRowBytes(string outputFileName, int rawRow) { using (MemoryStream memoryStream = new MemoryStream()) { using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8)) { for (int rawColumn = 0; rawColumn < RawColumnCount; rawColumn++) { if (IsCommentColumn(rawColumn)) { continue; } try { m_DataProcessor[rawColumn].WriteToStream(this, binaryWriter, GetValue(rawRow, rawColumn)); } catch { if (m_DataProcessor[rawColumn].IsId || string.IsNullOrEmpty(GetDefaultValue(rawColumn))) { Debug.LogError(Utility.Text.Format("Parse raw value failure. OutputFileName='{0}' RawRow='{1}' RowColumn='{2}' Name='{3}' Type='{4}' RawValue='{5}'", outputFileName, rawRow, rawColumn, GetName(rawColumn), GetLanguageKeyword(rawColumn), GetValue(rawRow, rawColumn))); return null; } else { Debug.LogWarning(Utility.Text.Format("Parse raw value failure, will try default value. OutputFileName='{0}' RawRow='{1}' RowColumn='{2}' Name='{3}' Type='{4}' RawValue='{5}'", outputFileName, rawRow, rawColumn, GetName(rawColumn), GetLanguageKeyword(rawColumn), GetValue(rawRow, rawColumn))); try { m_DataProcessor[rawColumn].WriteToStream(this, binaryWriter, GetDefaultValue(rawColumn)); } catch { Debug.LogError(Utility.Text.Format("Parse default value failure. OutputFileName='{0}' RawRow='{1}' RowColumn='{2}' Name='{3}' Type='{4}' RawValue='{5}'", outputFileName, rawRow, rawColumn, GetName(rawColumn), GetLanguageKeyword(rawColumn), GetComment(rawColumn))); return null; } } } } return memoryStream.ToArray(); } } } } }