using System; using System.Collections.Generic; using System.Text; using JetBrains.Annotations; using UnityEditor.Graphing; using System.Globalization; using UnityEngine.Profiling; namespace UnityEditor.ShaderGraph { struct ShaderStringMapping { public AbstractMaterialNode node { get; set; } // public List nodes { get; set; } public int startIndex { get; set; } public int count { get; set; } } class ShaderStringBuilder : IDisposable { enum ScopeType { Indent, Block, BlockSemicolon } StringBuilder m_StringBuilder; Stack m_ScopeStack; int m_IndentationLevel; ShaderStringMapping m_CurrentMapping; List m_Mappings; bool m_HumanReadable; const string k_IndentationString = " "; const string k_NewLineString = "\n"; internal AbstractMaterialNode currentNode { get { return m_CurrentMapping.node; } set { m_CurrentMapping.count = m_StringBuilder.Length - m_CurrentMapping.startIndex; if (m_CurrentMapping.count > 0) m_Mappings.Add(m_CurrentMapping); m_CurrentMapping.node = value; m_CurrentMapping.startIndex = m_StringBuilder.Length; m_CurrentMapping.count = 0; } } internal List mappings { get { return m_Mappings; } } public ShaderStringBuilder(int indentationLevel = 0, int stringBuilderSize = 8192, bool humanReadable = false) { IncreaseIndent(indentationLevel); m_StringBuilder = new StringBuilder(stringBuilderSize); m_ScopeStack = new Stack(); m_Mappings = new List(); m_CurrentMapping = new ShaderStringMapping(); m_HumanReadable = humanReadable; } public void AppendNewLine() { m_StringBuilder.Append(k_NewLineString); } private void AppendLine(string value, int startIndex, int count) { if (value.Length > 0) { TryAppendIndentation(); m_StringBuilder.Append(value, startIndex, count); } AppendNewLine(); } public void AppendLine(string value) { if (!string.IsNullOrEmpty(value)) { TryAppendIndentation(); m_StringBuilder.Append(value); } AppendNewLine(); } [StringFormatMethod("formatString")] public void AppendLine(string formatString, params object[] args) { TryAppendIndentation(); m_StringBuilder.AppendFormat(CultureInfo.InvariantCulture, formatString, args); AppendNewLine(); } static readonly char[] LineSeparators = new[] { '\n', '\r'}; public void AppendLines(string lines) { if (string.IsNullOrEmpty(lines)) return; int startSearchIndex = 0; int indexOfNextBreak = lines.IndexOfAny(LineSeparators); while (indexOfNextBreak >= 0) { AppendLine(lines, startSearchIndex, indexOfNextBreak - startSearchIndex); startSearchIndex = indexOfNextBreak + 1; indexOfNextBreak = lines.IndexOfAny(LineSeparators, startSearchIndex); } if (startSearchIndex < lines.Length) { AppendLine(lines, startSearchIndex, lines.Length - startSearchIndex); } } public void Append(string value) { m_StringBuilder.Append(value); } public void Append(string value, int start, int count) { m_StringBuilder.Append(value, start, count); } [StringFormatMethod("formatString")] public void Append(string formatString, params object[] args) { m_StringBuilder.AppendFormat(formatString, args); } public void AppendSpaces(int count) { m_StringBuilder.Append(' ', count); } public void TryAppendIndentation() { if (m_HumanReadable) { for (var i = 0; i < m_IndentationLevel; i++) m_StringBuilder.Append(k_IndentationString); } } public IDisposable IndentScope() { m_ScopeStack.Push(ScopeType.Indent); IncreaseIndent(); return this; } public IDisposable BlockScope() { AppendLine("{"); IncreaseIndent(); m_ScopeStack.Push(ScopeType.Block); return this; } public IDisposable BlockSemicolonScope() { AppendLine("{"); IncreaseIndent(); m_ScopeStack.Push(ScopeType.BlockSemicolon); return this; } public void IncreaseIndent() { m_IndentationLevel++; } public void IncreaseIndent(int level) { for (var i = 0; i < level; i++) IncreaseIndent(); } public void DecreaseIndent() { m_IndentationLevel--; } public void DecreaseIndent(int level) { for (var i = 0; i < level; i++) DecreaseIndent(); } public void Dispose() { if (m_ScopeStack.Count == 0) return; switch (m_ScopeStack.Pop()) { case ScopeType.Indent: DecreaseIndent(); break; case ScopeType.Block: DecreaseIndent(); AppendLine("}"); break; case ScopeType.BlockSemicolon: DecreaseIndent(); AppendLine("};"); break; } } public void Concat(ShaderStringBuilder other) { // First re-add all the mappings from `other`, such that their mappings are transformed. foreach (var mapping in other.m_Mappings) { currentNode = mapping.node; // Use `AppendLines` to indent according to the current indentation. if (m_HumanReadable) { AppendLines(other.ToString(mapping.startIndex, mapping.count)); } else { Append(other.ToString(mapping.startIndex, mapping.count)); } } currentNode = other.currentNode; if (m_HumanReadable) { AppendLines(other.ToString(other.m_CurrentMapping.startIndex, other.length - other.m_CurrentMapping.startIndex)); } else { Append(other.ToString(other.m_CurrentMapping.startIndex, other.length - other.m_CurrentMapping.startIndex)); } } public void ReplaceInCurrentMapping(string oldValue, string newValue) { Profiler.BeginSample("ReplaceInCurrentMapping"); int start = m_CurrentMapping.startIndex; int end = m_StringBuilder.Length - start; m_StringBuilder.Replace(oldValue, newValue, start, end); Profiler.EndSample(); } public void Replace(string oldValue, string newValue, int start, int end) { m_StringBuilder.Replace(oldValue, newValue, start, end); } public string ToCodeBlock() { // Remove new line if (m_StringBuilder.Length > 0) m_StringBuilder.Length = m_StringBuilder.Length - 1; if (m_HumanReadable) { // Set indentations m_StringBuilder.Replace(Environment.NewLine, Environment.NewLine + k_IndentationString); } return m_StringBuilder.ToString(); } public override string ToString() { return m_StringBuilder.ToString(); } public string ToString(int startIndex, int length) { return m_StringBuilder.ToString(startIndex, length); } internal void Clear() { m_StringBuilder.Length = 0; } internal int length { get { return m_StringBuilder.Length; } set { m_StringBuilder.Length = value; } } } }