// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; #nullable enable namespace UnrealGameSync { class PrefixedTextWriter : ILogger { readonly string _prefix; readonly ILogger _inner; public PrefixedTextWriter(string inPrefix, ILogger inInner) { _prefix = inPrefix; _inner = inInner; } public IDisposable? BeginScope(TState state) where TState : notnull => _inner.BeginScope(state); public bool IsEnabled(LogLevel logLevel) => _inner.IsEnabled(logLevel); public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { _inner.Log(logLevel, eventId, state, exception, (state, exception) => _prefix + formatter(state, exception)); } } public class ProgressValue { Tuple _state = null!; readonly Stack> _ranges = new Stack>(); public ProgressValue() { Clear(); } public void Clear() { _state = new Tuple("Starting...", 0.0f); _ranges.Clear(); _ranges.Push(new Tuple(0.0f, 1.0f)); } public Tuple Current => _state; public void Set(string message) { if (_ranges.Count == 1) { _state = new Tuple(message, _state.Item2); } } public void Set(string message, float fraction) { if (_ranges.Count == 1) { _state = new Tuple(message, RelativeToAbsoluteFraction(fraction)); } else { _state = new Tuple(_state.Item1, RelativeToAbsoluteFraction(fraction)); } } public void Set(float fraction) { _state = new Tuple(_state.Item1, RelativeToAbsoluteFraction(fraction)); } public void Increment(float fraction) { Set(_state.Item2 + RelativeToAbsoluteFraction(fraction)); } public void Push(float maxFraction) { _ranges.Push(new Tuple(_state.Item2, RelativeToAbsoluteFraction(maxFraction))); } public void Pop() { if (_ranges.Count > 1) { _state = new Tuple(_state.Item1, _ranges.Pop().Item2); } } float RelativeToAbsoluteFraction(float fraction) { Tuple range = _ranges.Peek(); return range.Item1 + (range.Item2 - range.Item1) * fraction; } } static class ProgressTextWriter { const string DirectivePrefix = "@progress "; public static string? ParseLine(string line, ProgressValue value) { string trimLine = line.Trim(); if (trimLine.StartsWith(DirectivePrefix, StringComparison.Ordinal)) { // Line that just contains a progress directive bool skipLine = false; ProcessInternal(trimLine.Substring(DirectivePrefix.Length), ref skipLine, value); return null; } else { bool skipLine = false; string remainingLine = line; // Look for a progress directive at the end of a line, in square brackets if (trimLine.EndsWith("]", StringComparison.Ordinal)) { for (int lastIdx = trimLine.Length - 2; lastIdx >= 0 && trimLine[lastIdx] != ']'; lastIdx--) { if (trimLine[lastIdx] == '[') { string directiveSubstring = trimLine.Substring(lastIdx + 1, trimLine.Length - lastIdx - 2); if (directiveSubstring.StartsWith(DirectivePrefix, StringComparison.Ordinal)) { ProcessInternal(directiveSubstring.Substring(DirectivePrefix.Length), ref skipLine, value); remainingLine = line.Substring(0, lastIdx).TrimEnd(); } break; } } } if (skipLine) { return null; } else { return remainingLine; } } } static void ProcessInternal(string line, ref bool skipLine, ProgressValue value) { List tokens = ParseTokens(line); for (int tokenIdx = 0; tokenIdx < tokens.Count;) { float fraction; if (ReadFraction(tokens, ref tokenIdx, out fraction)) { value.Set(fraction); } else if (tokens[tokenIdx] == "push") { tokenIdx++; if (ReadFraction(tokens, ref tokenIdx, out fraction)) { value.Push(fraction); } } else if (tokens[tokenIdx] == "pop") { tokenIdx++; value.Pop(); } else if (tokens[tokenIdx] == "increment") { tokenIdx++; if (ReadFraction(tokens, ref tokenIdx, out fraction)) { value.Increment(fraction); } } else if (tokens[tokenIdx] == "skipline") { tokenIdx++; skipLine = true; } else if (tokens[tokenIdx].Length >= 2 && (tokens[tokenIdx][0] == '\'' || tokens[tokenIdx][0] == '\"') && tokens[tokenIdx].Last() == tokens[tokenIdx].First()) { string message = tokens[tokenIdx++]; value.Set(message.Substring(1, message.Length - 2)); } else { tokenIdx++; } } } static List ParseTokens(string line) { List tokens = new List(); for (int idx = 0; ;) { // Skip whitespace while (idx < line.Length && Char.IsWhiteSpace(line[idx])) { idx++; } if (idx == line.Length) { break; } // Read the next token if (Char.IsLetterOrDigit(line[idx])) { int startIdx = idx++; while (idx < line.Length && Char.IsLetterOrDigit(line[idx])) { idx++; } tokens.Add(line.Substring(startIdx, idx - startIdx)); } else if (line[idx] == '\'' || line[idx] == '\"') { int startIdx = idx++; while (idx < line.Length && line[idx] != line[startIdx]) { idx++; } tokens.Add(line.Substring(startIdx, ++idx - startIdx)); } else { tokens.Add(line.Substring(idx++, 1)); } } return tokens; } static bool ReadFraction(List tokens, ref int tokenIdx, out float fraction) { // Read a fraction in the form x% if (tokenIdx + 2 <= tokens.Count && tokens[tokenIdx + 1] == "%") { int numerator; if (Int32.TryParse(tokens[tokenIdx], out numerator)) { fraction = (float)numerator / 100.0f; tokenIdx += 2; return true; } } // Read a fraction in the form x/y if (tokenIdx + 3 <= tokens.Count && tokens[tokenIdx + 1] == "/") { int numerator, denominator; if (Int32.TryParse(tokens[tokenIdx], out numerator) && Int32.TryParse(tokens[tokenIdx + 2], out denominator)) { fraction = (float)numerator / (float)denominator; tokenIdx += 3; return true; } } fraction = 0.0f; return false; } } }