diff options
Diffstat (limited to 'Ryujinx.Common')
16 files changed, 402 insertions, 9 deletions
diff --git a/Ryujinx.Common/StructIOExtension.cs b/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs index 8671b192..49af946f 100644 --- a/Ryujinx.Common/StructIOExtension.cs +++ b/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs @@ -1,14 +1,13 @@ using System; -using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; -using System.Text; namespace Ryujinx.Common { - public static class StructIOExtension + public static class BinaryReaderExtensions { - public unsafe static T ReadStruct<T>(this BinaryReader reader) where T : struct + public unsafe static T ReadStruct<T>(this BinaryReader reader) + where T : struct { int size = Marshal.SizeOf<T>(); @@ -20,7 +19,8 @@ namespace Ryujinx.Common } } - public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value) where T : struct + public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value) + where T : struct { long size = Marshal.SizeOf<T>(); diff --git a/Ryujinx.Common/Extensions/EnumExtensions.cs b/Ryujinx.Common/Extensions/EnumExtensions.cs new file mode 100644 index 00000000..560af882 --- /dev/null +++ b/Ryujinx.Common/Extensions/EnumExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Common +{ + public static class EnumExtensions + { + public static T[] GetValues<T>() + { + return (T[])Enum.GetValues(typeof(T)); + } + } +} diff --git a/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs new file mode 100644 index 00000000..0c4396e7 --- /dev/null +++ b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using System.Text; + +namespace Ryujinx.Common.Logging +{ + internal class DefaultLogFormatter : ILogFormatter + { + private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>(); + + public string Format(LogEventArgs args) + { + StringBuilder sb = _stringBuilderPool.Allocate(); + + try + { + sb.Clear(); + + sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time); + sb.Append(" | "); + sb.AppendFormat("{0:d4}", args.ThreadId); + sb.Append(' '); + sb.Append(args.Message); + + if (args.Data != null) + { + PropertyInfo[] props = args.Data.GetType().GetProperties(); + + sb.Append(' '); + + foreach (var prop in props) + { + sb.Append(prop.Name); + sb.Append(": "); + sb.Append(prop.GetValue(args.Data)); + sb.Append(" - "); + } + + // We remove the final '-' from the string + if (props.Length > 0) + { + sb.Remove(sb.Length - 3, 3); + } + } + + return sb.ToString(); + } + finally + { + _stringBuilderPool.Release(sb); + } + } + } +} diff --git a/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs b/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs new file mode 100644 index 00000000..9a55bc6b --- /dev/null +++ b/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Common.Logging +{ + interface ILogFormatter + { + string Format(LogEventArgs args); + } +} diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs index f20347b6..66a83b37 100644 --- a/Ryujinx.Common/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -2,6 +2,7 @@ namespace Ryujinx.Common.Logging { public enum LogClass { + Application, Audio, Cpu, Font, diff --git a/Ryujinx.Common/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs index 35ca416b..88ebe473 100644 --- a/Ryujinx.Common/Logging/Logger.cs +++ b/Ryujinx.Common/Logging/Logger.cs @@ -1,8 +1,7 @@ using System; +using System.Collections.Generic; using System.Diagnostics; -using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; using System.Threading; namespace Ryujinx.Common.Logging @@ -14,9 +13,9 @@ namespace Ryujinx.Common.Logging private static readonly bool[] m_EnabledLevels; private static readonly bool[] m_EnabledClasses; - public static event EventHandler<LogEventArgs> Updated; + private static readonly List<ILogTarget> m_LogTargets; - public static bool EnableFileLog { get; set; } + public static event EventHandler<LogEventArgs> Updated; static Logger() { @@ -33,9 +32,30 @@ namespace Ryujinx.Common.Logging m_EnabledClasses[index] = true; } + m_LogTargets = new List<ILogTarget>(); + m_Time = Stopwatch.StartNew(); } + public static void AddTarget(ILogTarget target) + { + m_LogTargets.Add(target); + + Updated += target.Log; + } + + public static void Shutdown() + { + Updated = null; + + foreach(var target in m_LogTargets) + { + target.Dispose(); + } + + m_LogTargets.Clear(); + } + public static void SetEnable(LogLevel logLevel, bool enabled) { m_EnabledLevels[(int)logLevel] = enabled; diff --git a/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs new file mode 100644 index 00000000..a805a83b --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Common.Logging +{ + public enum AsyncLogTargetOverflowAction + { + /// <summary> + /// Block until there's more room in the queue + /// </summary> + Block = 0, + + /// <summary> + /// Discard the overflowing item + /// </summary> + Discard = 1 + } + + public class AsyncLogTargetWrapper : ILogTarget + { + private ILogTarget _target; + + private Thread _messageThread; + + private BlockingCollection<LogEventArgs> _messageQueue; + + private readonly int _overflowTimeout; + + public AsyncLogTargetWrapper(ILogTarget target) + : this(target, -1, AsyncLogTargetOverflowAction.Block) + { } + + public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction) + { + _target = target; + _messageQueue = new BlockingCollection<LogEventArgs>(queueLimit); + _overflowTimeout = overflowAction == AsyncLogTargetOverflowAction.Block ? -1 : 0; + + _messageThread = new Thread(() => { + while (!_messageQueue.IsCompleted) + { + try + { + _target.Log(this, _messageQueue.Take()); + } + catch (InvalidOperationException) + { + // IOE means that Take() was called on a completed collection. + // Some other thread can call CompleteAdding after we pass the + // IsCompleted check but before we call Take. + // We can simply catch the exception since the loop will break + // on the next iteration. + } + } + }); + + _messageThread.IsBackground = true; + _messageThread.Start(); + } + + public void Log(object sender, LogEventArgs e) + { + if (!_messageQueue.IsAddingCompleted) + { + _messageQueue.TryAdd(e, _overflowTimeout); + } + } + + public void Dispose() + { + _messageQueue.CompleteAdding(); + _messageThread.Join(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs new file mode 100644 index 00000000..871076a4 --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Common.Logging +{ + public class ConsoleLogTarget : ILogTarget + { + private static readonly ConcurrentDictionary<LogLevel, ConsoleColor> _logColors; + + private readonly ILogFormatter _formatter; + + static ConsoleLogTarget() + { + _logColors = new ConcurrentDictionary<LogLevel, ConsoleColor> { + [ LogLevel.Stub ] = ConsoleColor.DarkGray, + [ LogLevel.Info ] = ConsoleColor.White, + [ LogLevel.Warning ] = ConsoleColor.Yellow, + [ LogLevel.Error ] = ConsoleColor.Red + }; + } + + public ConsoleLogTarget() + { + _formatter = new DefaultLogFormatter(); + } + + public void Log(object sender, LogEventArgs args) + { + if (_logColors.TryGetValue(args.Level, out ConsoleColor color)) + { + Console.ForegroundColor = color; + + Console.WriteLine(_formatter.Format(args)); + + Console.ResetColor(); + } + else + { + Console.WriteLine(_formatter.Format(args)); + } + } + + public void Dispose() + { + Console.ResetColor(); + } + } +} diff --git a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs new file mode 100644 index 00000000..85dc8249 --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -0,0 +1,36 @@ +using System.IO; +using System.Text; + +namespace Ryujinx.Common.Logging +{ + public class FileLogTarget : ILogTarget + { + private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>(); + + private readonly StreamWriter _logWriter; + private readonly ILogFormatter _formatter; + + public FileLogTarget(string path) + : this(path, FileShare.Read, FileMode.Append) + { } + + public FileLogTarget(string path, FileShare fileShare, FileMode fileMode) + { + _logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare)); + _formatter = new DefaultLogFormatter(); + } + + public void Log(object sender, LogEventArgs args) + { + _logWriter.WriteLine(_formatter.Format(args)); + _logWriter.Flush(); + } + + public void Dispose() + { + _logWriter.WriteLine("---- End of Log ----"); + _logWriter.Flush(); + _logWriter.Dispose(); + } + } +} diff --git a/Ryujinx.Common/Logging/Targets/ILogTarget.cs b/Ryujinx.Common/Logging/Targets/ILogTarget.cs new file mode 100644 index 00000000..261c5e64 --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/ILogTarget.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.Common.Logging +{ + public interface ILogTarget : IDisposable + { + void Log(object sender, LogEventArgs args); + } +} diff --git a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs new file mode 100644 index 00000000..410394aa --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs @@ -0,0 +1,35 @@ +using System.IO; +using Utf8Json; + +namespace Ryujinx.Common.Logging +{ + public class JsonLogTarget : ILogTarget + { + private Stream _stream; + private bool _leaveOpen; + + public JsonLogTarget(Stream stream) + { + _stream = stream; + } + + public JsonLogTarget(Stream stream, bool leaveOpen) + { + _stream = stream; + _leaveOpen = leaveOpen; + } + + public void Log(object sender, LogEventArgs e) + { + JsonSerializer.Serialize(_stream, e); + } + + public void Dispose() + { + if (!_leaveOpen) + { + _stream.Dispose(); + } + } + } +} diff --git a/Ryujinx.Common/Pools/ObjectPool.cs b/Ryujinx.Common/Pools/ObjectPool.cs new file mode 100644 index 00000000..dba671bb --- /dev/null +++ b/Ryujinx.Common/Pools/ObjectPool.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading; + +namespace Ryujinx.Common +{ + public class ObjectPool<T> + where T : class + { + private T _firstItem; + private readonly T[] _items; + + private readonly Func<T> _factory; + + public ObjectPool(Func<T> factory, int size) + { + _items = new T[size - 1]; + _factory = factory; + } + + public T Allocate() + { + var instance = _firstItem; + + if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance)) + { + instance = AllocateInternal(); + } + + return instance; + } + + private T AllocateInternal() + { + var items = _items; + + for (int i = 0; i < items.Length; i++) + { + var instance = items[i]; + + if (instance != null && instance == Interlocked.CompareExchange(ref items[i], null, instance)) + { + return instance; + } + } + + return _factory(); + } + + public void Release(T obj) + { + if (_firstItem == null) + { + _firstItem = obj; + } + else + { + ReleaseInternal(obj); + } + } + + private void ReleaseInternal(T obj) + { + var items = _items; + + for (int i = 0; i < items.Length; i++) + { + if (items[i] == null) + { + items[i] = obj; + break; + } + } + } + } +} diff --git a/Ryujinx.Common/Pools/SharedPools.cs b/Ryujinx.Common/Pools/SharedPools.cs new file mode 100644 index 00000000..b4860b85 --- /dev/null +++ b/Ryujinx.Common/Pools/SharedPools.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Common +{ + public static class SharedPools + { + private static class DefaultPool<T> + where T : class, new() + { + public static readonly ObjectPool<T> Instance = new ObjectPool<T>(() => new T(), 20); + } + + public static ObjectPool<T> Default<T>() + where T : class, new() + { + return DefaultPool<T>.Instance; + } + } +} diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj index 5c9293b7..bba481e6 100644 --- a/Ryujinx.Common/Ryujinx.Common.csproj +++ b/Ryujinx.Common/Ryujinx.Common.csproj @@ -13,4 +13,8 @@ <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> + <ItemGroup> + <PackageReference Include="Utf8Json" Version="1.3.7" /> + </ItemGroup> + </Project> diff --git a/Ryujinx.Common/BitUtils.cs b/Ryujinx.Common/Utilities/BitUtils.cs index 135b397d..135b397d 100644 --- a/Ryujinx.Common/BitUtils.cs +++ b/Ryujinx.Common/Utilities/BitUtils.cs diff --git a/Ryujinx.Common/HexUtils.cs b/Ryujinx.Common/Utilities/HexUtils.cs index 63587cea..63587cea 100644 --- a/Ryujinx.Common/HexUtils.cs +++ b/Ryujinx.Common/Utilities/HexUtils.cs |
