aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Common
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Common')
-rw-r--r--Ryujinx.Common/Extensions/BinaryReaderExtensions.cs (renamed from Ryujinx.Common/StructIOExtension.cs)10
-rw-r--r--Ryujinx.Common/Extensions/EnumExtensions.cs12
-rw-r--r--Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs53
-rw-r--r--Ryujinx.Common/Logging/Formatters/ILogFormatter.cs7
-rw-r--r--Ryujinx.Common/Logging/LogClass.cs1
-rw-r--r--Ryujinx.Common/Logging/Logger.cs28
-rw-r--r--Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs76
-rw-r--r--Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs48
-rw-r--r--Ryujinx.Common/Logging/Targets/FileLogTarget.cs36
-rw-r--r--Ryujinx.Common/Logging/Targets/ILogTarget.cs9
-rw-r--r--Ryujinx.Common/Logging/Targets/JsonLogTarget.cs35
-rw-r--r--Ryujinx.Common/Pools/ObjectPool.cs75
-rw-r--r--Ryujinx.Common/Pools/SharedPools.cs17
-rw-r--r--Ryujinx.Common/Ryujinx.Common.csproj4
-rw-r--r--Ryujinx.Common/Utilities/BitUtils.cs (renamed from Ryujinx.Common/BitUtils.cs)0
-rw-r--r--Ryujinx.Common/Utilities/HexUtils.cs (renamed from Ryujinx.Common/HexUtils.cs)0
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