diff --git a/sourcegeneration/MonologueSourceGenerator/LogGenerator.cs b/sourcegeneration/MonologueSourceGenerator/LogGenerator.cs index d09c4a6a..e0bed420 100644 --- a/sourcegeneration/MonologueSourceGenerator/LogGenerator.cs +++ b/sourcegeneration/MonologueSourceGenerator/LogGenerator.cs @@ -1,12 +1,24 @@ using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Monologue.SourceGenerator; -internal record LogData(string? PreComputed, string? GetOperation, string? Path, string? Type); +internal enum DeclarationType +{ + Logged, + Struct, + Protobuf, + Other +} + + +internal record LogAttributeInfo(string Path, string LogLevel, string LogType, bool UseProtobuf); + +internal record LogData(string GetOperation, string? Type, DeclarationType DecelType, LogAttributeInfo AttributeInfo); internal record ClassData(ImmutableArray LoggedItems, string Name, string ClassDeclaration, string? Namespace); @@ -48,20 +60,49 @@ public class LogGenerator : IIncrementalGenerator if (attributeClass.ToDisplayString() == "Monologue.LogAttribute") { token.ThrowIfCancellationRequested(); + + string path = member.Name; + bool useProtobuf = false; + string logTypeEnum = "Monologue.LogType.Nt | Monologue.LogType.File"; + string logLevel = "Monologue.LogLevel.Default"; + + // Get the log attribute + foreach (var named in attribute.NamedArguments) + { + if (named.Key == "Key") + { + if (!named.Value.IsNull) + { + path = SymbolDisplay.FormatPrimitive(named.Value.Value!, false, false); + } + } + else if (named.Key == "LogLevel") + { + logLevel = named.Value.ToCSharpString(); + } + else if (named.Key == "LogType") + { + logTypeEnum = named.Value.ToCSharpString(); + } + else if (named.Key == "UseProtobuf") + { + useProtobuf = (bool)named.Value.Value!; + } + } + + var attributeInfo = new LogAttributeInfo(path, logLevel, logTypeEnum, useProtobuf); + string getOperation; - string defaultPathName; ITypeSymbol logType; // This is ours if (member is IFieldSymbol field) { getOperation = field.Name; - defaultPathName = field.Name; logType = field.Type; } else if (member is IPropertySymbol property) { getOperation = property.Name; - defaultPathName = property.Name; logType = property.Type; } else if (member is IMethodSymbol method) @@ -76,7 +117,6 @@ public class LogGenerator : IIncrementalGenerator } getOperation = $"{method.Name}()"; - defaultPathName = method.Name; logType = method.ReturnType; } else @@ -84,7 +124,7 @@ public class LogGenerator : IIncrementalGenerator throw new InvalidOperationException("Field is not loggable"); } - var fullOperation = ComputeOperation(logType, getOperation, defaultPathName); + var fullOperation = ComputeOperation(logType, getOperation, attributeInfo); token.ThrowIfCancellationRequested(); loggableMembers.Add(fullOperation); break; @@ -98,36 +138,41 @@ public class LogGenerator : IIncrementalGenerator return new ClassData(loggableMembers.ToImmutable(), $"{classSymbol.ContainingNamespace}{classSymbol.ToDisplayString(fmt)}{classSymbol.MetadataName}", typeBuilder.ToString(), ns); } - private static LogData ComputeOperation(ITypeSymbol logType, string getOp, string path) + private static LogData ComputeOperation(ITypeSymbol logType, string getOp, LogAttributeInfo attributeInfo) { if (logType.GetAttributes().Where(x => x.AttributeClass?.ToDisplayString() == "Monologue.GenerateLogAttribute").Any()) { - return new($"{getOp}.UpdateMonologue($\"{{path}}/{path}\", logger);", null, null, null); + return new LogData(getOp, null, DeclarationType.Logged, attributeInfo); } if (logType.AllInterfaces.Where(x => x.ToDisplayString() == "Monologue.ILogged").Any()) { - return new($"{getOp}.UpdateMonologue($\"{{path}}/{path}\", logger);", null, null, null); - //return $"{getOp}.UpdateMonologue($\"{{path}}/{path}\", logger);"; + return new LogData(getOp, null, DeclarationType.Logged, attributeInfo); } var fmt = new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters); - var fullName = logType.ToDisplayString(fmt); - var structName = $"WPIUtil.Serialization.Struct.IStructSerializable<{fullName}>"; - var protobufName = $"WPIUtil.Serialization.Protobuf.IProtobufSerializable<{fullName}>"; + var fullTypeName = logType.ToDisplayString(fmt); + var structName = $"WPIUtil.Serialization.Struct.IStructSerializable<{fullTypeName}>"; + var protobufName = $"WPIUtil.Serialization.Protobuf.IProtobufSerializable<{fullTypeName}>"; + foreach (var inf in logType.AllInterfaces) { var interfaceName = inf.ToDisplayString(); - // For now prefer struct if (interfaceName == structName) { - return new($"logger.LogStruct($\"{{path}}/{path}\", LogType.Nt, {getOp});", null, null, null); + if (!attributeInfo.UseProtobuf) + { + return new LogData(getOp, null, DeclarationType.Struct, attributeInfo); + } } else if (interfaceName == protobufName) { - return new($"logger.LogProto($\"{{path}}/{path}\", LogType.Nt, {getOp});", null, null, null); + if (attributeInfo.UseProtobuf) + { + return new LogData(getOp, null, DeclarationType.Protobuf, attributeInfo); + } } } - return new(null, getOp, path, fullName); + return new LogData(getOp, fullTypeName, DeclarationType.Other, attributeInfo); } public void Initialize(IncrementalGeneratorInitializationContext context) @@ -145,14 +190,22 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static void ConstructCall(LogData data, StringBuilder builder) { - if (data.PreComputed is not null) - { + builder.Append(" "); - builder.AppendLine($" {data.PreComputed}"); - return; + switch (data.DecelType) + { + case DeclarationType.Logged: + builder.AppendLine($"{data.GetOperation}?.UpdateMonologue($\"{{path}}/{data.AttributeInfo.Path}\", logger);"); + return; + case DeclarationType.Struct: + builder.AppendLine($"logger.LogStruct($\"{{path}}/{data.AttributeInfo.Path}\", {data.AttributeInfo.LogType}, {data.GetOperation});"); + return; + case DeclarationType.Protobuf: + builder.AppendLine($"logger.LogProto($\"{{path}}/{data.AttributeInfo.Path}\", {data.AttributeInfo.LogType}, {data.GetOperation});"); + return; } - var ret = data.Type switch + (string? LogMethod, string Cast, string Conversion) ret = data.Type switch { "System.Single" => ("LogFloat", "", ""), "System.Double" => ("LogDouble", "", ""), @@ -189,7 +242,7 @@ static void ConstructCall(LogData data, StringBuilder builder) _ => (data.Type, "", "") }; - builder.AppendLine($" logger.{ret.Item1}($\"{{path}}/{data.Path}\", LogType.Nt, {ret.Item2}{data.GetOperation}{ret.Item3});"); + builder.AppendLine($"logger.{ret.LogMethod}($\"{{path}}/{data.AttributeInfo.Path}\", {data.AttributeInfo.LogType}, {ret.Cast}{data.GetOperation}{ret.Conversion});"); } static void Execute(ClassData? classData, SourceProductionContext context) diff --git a/src/thirdparty/Monologue/Attributes.cs b/src/thirdparty/Monologue/Attributes.cs index d9778875..dbe324d1 100644 --- a/src/thirdparty/Monologue/Attributes.cs +++ b/src/thirdparty/Monologue/Attributes.cs @@ -7,9 +7,8 @@ public sealed class LogAttribute : Attribute { public string Key { get; init; } = ""; public LogLevel LogLevel { get; init; } = LogLevel.Default; - public LogType LogType { get; init; } = LogType.Nt; - public bool Once { get; init; } = false; - public bool PreferProtobuf { get; init; } = false; + public LogType LogType { get; init; } = LogType.Nt | LogType.File; + public bool UseProtobuf { get; init; } = false; } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface)] diff --git a/src/thirdparty/Monologue/LogType.cs b/src/thirdparty/Monologue/LogType.cs index bc25cdd1..1b5c34c9 100644 --- a/src/thirdparty/Monologue/LogType.cs +++ b/src/thirdparty/Monologue/LogType.cs @@ -7,5 +7,6 @@ public enum LogType { None = 0, File = 1, - Nt = 2 + Nt = 2, + Once = 4, } diff --git a/src/thirdparty/Monologue/Monologuer.cs b/src/thirdparty/Monologue/Monologuer.cs index 36fe686f..ceee3898 100644 --- a/src/thirdparty/Monologue/Monologuer.cs +++ b/src/thirdparty/Monologue/Monologuer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using NetworkTables; using WPIUtil.Logging; @@ -30,17 +31,21 @@ public static void UpdateAll(ILogged logged) private readonly DataLog log = null!; - private readonly Dictionary integerLogs = []; - private readonly Dictionary booleanLogs = []; - private readonly Dictionary structLogs = []; + private record struct TypedLogs(TNT? topic, TDataLog? logEntry) where TNT : class, IPublisher where TDataLog : DataLogEntry; - public void LogBoolean(string path, LogType logType, bool value) + private readonly Dictionary> integerLogs = []; + private readonly Dictionary> booleanLogs = []; + private readonly Dictionary> doubleLogs = []; + private readonly Dictionary> structLogs = []; + private readonly Dictionary> protobufLogs = []; + + public void LogBoolean(string path, LogType logType, bool value, LogLevel logLevel = LogLevel.Default) { - if (logType == LogType.None) + ref var logs = ref CheckDoLog(path, ref logType, logLevel, booleanLogs); + if (Unsafe.IsNullRef(ref logs)) { return; } - ref var logs = ref CollectionsMarshal.GetValueRefOrAddDefault(booleanLogs, path, out _); if (logType.HasFlag(LogType.Nt)) { logs.topic ??= NetworkTableInstance.Default.GetBooleanTopic(path).Publish(PubSubOptions.None); @@ -53,13 +58,13 @@ public void LogBoolean(string path, LogType logType, bool value) } } - public void LogInteger(string path, LogType logType, long value) + public void LogInteger(string path, LogType logType, long value, LogLevel logLevel = LogLevel.Default) { - if (logType == LogType.None) + ref var logs = ref CheckDoLog(path, ref logType, logLevel, integerLogs); + if (Unsafe.IsNullRef(ref logs)) { return; } - ref var logs = ref CollectionsMarshal.GetValueRefOrAddDefault(integerLogs, path, out _); if (logType.HasFlag(LogType.Nt)) { logs.topic ??= NetworkTableInstance.Default.GetIntegerTopic(path).Publish(PubSubOptions.None); @@ -72,13 +77,13 @@ public void LogInteger(string path, LogType logType, long value) } } - public void LogStruct(string path, LogType logType, in T value) where T : IStructSerializable + public void LogStruct(string path, LogType logType, in T value, LogLevel logLevel = LogLevel.Default) where T : IStructSerializable { - if (logType == LogType.None) + ref var logs = ref CheckDoLog(path, ref logType, logLevel, structLogs); + if (Unsafe.IsNullRef(ref logs)) { return; } - ref var logs = ref CollectionsMarshal.GetValueRefOrAddDefault(structLogs, path, out _); if (logType.HasFlag(LogType.Nt)) { logs.topic ??= NetworkTableInstance.Default.GetStructTopic(path).Publish(PubSubOptions.None); @@ -93,13 +98,13 @@ public void LogStruct(string path, LogType logType, in T value) where T : ISt } } - public void LogProto(string path, LogType logType, in T value) where T : IProtobufSerializable + public void LogProto(string path, LogType logType, in T value, LogLevel logLevel = LogLevel.Default) where T : IProtobufSerializable { - if (logType == LogType.None) + ref var logs = ref CheckDoLog(path, ref logType, logLevel, protobufLogs); + if (Unsafe.IsNullRef(ref logs)) { return; } - ref var logs = ref CollectionsMarshal.GetValueRefOrAddDefault(structLogs, path, out _); if (logType.HasFlag(LogType.Nt)) { logs.topic ??= NetworkTableInstance.Default.GetProtobufTopic(path).Publish(PubSubOptions.None); @@ -114,59 +119,152 @@ public void LogProto(string path, LogType logType, in T value) where T : IPro } } - public void LogChar(string path, LogType logType, char value) + public void LogChar(string path, LogType logType, char value, LogLevel logLevel = LogLevel.Default) { - + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogFloat(string path, LogType logType, float value) + public void LogFloat(string path, LogType logType, float value, LogLevel logLevel = LogLevel.Default) { - + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogDouble(string path, LogType logType, double value) + public void LogDouble(string path, LogType logType, double value, LogLevel logLevel = LogLevel.Default) { - + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogString(string path, LogType logType, string? value) + public void LogString(string path, LogType logType, string? value, LogLevel logLevel = LogLevel.Default) { LogString(path, logType, value.AsSpan()); } - public void LogString(string path, LogType logType, ReadOnlySpan value) + public void LogString(string path, LogType logType, ReadOnlySpan value, LogLevel logLevel = LogLevel.Default) { - + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogBooleanArray(string path, LogType logType, ReadOnlySpan value) + public void LogBooleanArray(string path, LogType logType, ReadOnlySpan value, LogLevel logLevel = LogLevel.Default) { - + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogStringArray(string path, LogType logType, ReadOnlySpan value) + public void LogStringArray(string path, LogType logType, ReadOnlySpan value, LogLevel logLevel = LogLevel.Default) { - + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogRaw(string path, LogType logType, ReadOnlySpan value) + public void LogRaw(string path, LogType logType, ReadOnlySpan value, LogLevel logLevel = LogLevel.Default) { - + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogIntegerArray(string path, LogType logType, ReadOnlySpan value) + public void LogIntegerArray(string path, LogType logType, ReadOnlySpan value, LogLevel logLevel = LogLevel.Default) { - + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogFloatArray(string path, LogType logType, ReadOnlySpan value) + public void LogFloatArray(string path, LogType logType, ReadOnlySpan value, LogLevel logLevel = LogLevel.Default) { + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } + } + public void LogDoubleArray(string path, LogType logType, ReadOnlySpan value, LogLevel logLevel = LogLevel.Default) + { + ref var logs = ref CheckDoLog(path, ref logType, logLevel, doubleLogs); + if (Unsafe.IsNullRef(ref logs)) + { + return; + } } - public void LogDoubleArray(string path, LogType logType, ReadOnlySpan value) + private readonly HashSet sentOnceKeys = []; + + private ref TypedLogs CheckDoLog(string path, + ref LogType logType, LogLevel logLevel, Dictionary> dict) + where TNT : class, IPublisher + where TDataLog : DataLogEntry { + // If we have no log type, do nothing + if (logType == LogType.None) + { + return ref Unsafe.NullRef>(); + } + + // We have the once flag, we need to check if we've already sent the path + if ((logType & LogType.Once) != 0) + { + if (!sentOnceKeys.Add(path)) + { + return ref Unsafe.NullRef>(); + } + // Unconditionally write if we're a once flag. + + return ref CollectionsMarshal.GetValueRefOrAddDefault(dict, path, out _); + } + + // If we're override file only, we're unconditionally logging + if (logLevel == LogLevel.OverrideFileOnly) + { + return ref CollectionsMarshal.GetValueRefOrAddDefault(dict, path, out _); + } + + ref var element = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, path, out _); + + if (FileOnly) + { + // We need to prune the NT Entry + element.topic?.Dispose(); + element.topic = null; + + // If not file only is set, skip + if (logLevel == LogLevel.NotFileOnly) + { + return ref Unsafe.NullRef>(); + } + + // We're default in file only mode, force file only + logType = LogType.File; + } + // We've either forced us into file only, or we just want to handle what we are already doing + return ref element; } } diff --git a/test/monologue.test/TestTree.cs b/test/monologue.test/TestTree.cs index dabb9130..d53d4990 100644 --- a/test/monologue.test/TestTree.cs +++ b/test/monologue.test/TestTree.cs @@ -92,7 +92,7 @@ public partial record class GenerateRecordClass [GenerateLog] public partial class GenerateAllKnownTypes { - [Log] + [Log(LogType = LogType.None)] public bool boolVar; [Log] public char charVar;