Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix AOT compatibility issues in Infra #273

Merged
merged 15 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion Natsurainko.FluentLauncher/Services/Settings/SettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Windows.Storage;

namespace Natsurainko.FluentLauncher.Services.Settings;
Expand Down Expand Up @@ -133,9 +134,11 @@ public partial class SettingsService : SettingsContainer
[SettingItem(Default = 0u)]
public partial uint SettingsVersion { get; set; }


public SettingsService(ISettingsStorage storage) : base(storage)
{
// Configure JsonSerializerContext for NativeAOT-compatible JsonStringConverter
JsonStringConverterConfig.SerializerContext = SetingsJsonSerializerContext.Default;

var appsettings = ApplicationData.Current.LocalSettings;

// Migrate settings data structures from old versions
Expand Down Expand Up @@ -271,3 +274,15 @@ private static void MigrateFrom_2_3_0_0()
appsettings.Values["ActiveInstanceId"] = clientId;
}
}

[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(uint))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(float))]
[JsonSerializable(typeof(double))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(Windows.UI.Color))]
[JsonSerializable(typeof(WinUIEx.WindowState))]
internal partial class SetingsJsonSerializerContext : JsonSerializerContext
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,26 @@ private static void GenerateSettingItem(SettingItemInfo settingItemInfo, StringB

string defaultValue = "";
string converter = "";
string sourceTypeName = "";

// Parse default value and converter
foreach(var item in attribute.NamedArguments)
{
if (item.Key == "Default")
{
defaultValue = $", {item.Value.ToCSharpString()}";
}
else if (item.Key == "Converter")
converter = $", global::FluentLauncher.Infra.Settings.Converters.DataTypeConverters.GetConverter(typeof(global::{item.Value.Value}))";
{
if (item.Value.Value is not ITypeSymbol converterType)
continue;
INamedTypeSymbol? interfaceType = converterType.Interfaces.FirstOrDefault(syn => syn.Name.Contains("IDataTypeConverter"));
if (interfaceType is null) continue;
ITypeSymbol sourceType = interfaceType.TypeArguments[0];

sourceTypeName = $", {sourceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}";
converter = $", global::{item.Value.Value}.Instance";
}
}

// If default value is not provided, the property is nullable
Expand All @@ -142,8 +154,8 @@ private static void GenerateSettingItem(SettingItemInfo settingItemInfo, StringB
memberBuilder.Append($$"""
public partial {{propTypeName}}{{nullable}} {{propIdentifierName}}
{
get => GetValue<{{propTypeName}}{{nullable}}>(nameof({{propIdentifierName}}){{defaultValue}}{{converter}});
set => SetValue<{{propTypeName}}>(nameof({{propIdentifierName}}), value, {{propIdentifierName}}Changed{{converter}});
get => GetValue<{{propTypeName}}{{sourceTypeName}}>(nameof({{propIdentifierName}}){{defaultValue}}{{converter}});
set => SetValue<{{propTypeName}}{{sourceTypeName}}>(nameof({{propIdentifierName}}), value, {{propIdentifierName}}Changed{{converter}});
}

public event global::FluentLauncher.Infra.Settings.SettingChangedEventHandler? {{propIdentifierName}}Changed;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
Expand All @@ -10,49 +11,12 @@ namespace FluentLauncher.Infra.Settings.Converters;
/// ConvertFrom is used when converting an object from the displayed type to the stored type. <br/>
/// Convert is used when converting an object from the stored type to the displayed type.
/// </summary>
public interface IDataTypeConverter
/// <typeparam name="TSource">Type stored in the storage</typeparam>
/// <typeparam name="TTarget">Type used in the application</typeparam>
public interface IDataTypeConverter<TSource, TTarget>
{
/// <summary>
/// Type stored in the storage
/// </summary>
Type SourceType { get; }
/// <summary>
/// Type used in the application
/// </summary>
Type TargetType { get; }
TTarget Convert(TSource source);
TSource ConvertFrom(TTarget target);

object? Convert(object? source);
object? ConvertFrom(object? target);
}

public static class DataTypeConverters
{
public static Dictionary<Type, IDataTypeConverter> Converters { get; } = new();

/// <summary>
/// Returns an instance of the converter for the specified type.
/// </summary>
/// <param name="type">Type of the converter class</param>
/// <remarks>
/// Singletons of the converters are stored in the Converters dictionary.
/// </remarks>
public static IDataTypeConverter GetConverter(Type type)
{
// Checks if type is IDataTypeConverter
if (typeof(IDataTypeConverter).IsAssignableFrom(type))
{
// Checks if the converter is already registered
if (!Converters.ContainsKey(type))
{
// Creates an instance of the converter
var converter = (IDataTypeConverter)Activator.CreateInstance(type)!;
Converters.Add(type, converter);
}
return Converters[type];
}
else
{
throw new ArgumentException("The specified type is not a data type converter.");
}
}
static abstract IDataTypeConverter<TSource, TTarget> Instance { get; }
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace FluentLauncher.Infra.Settings.Converters;
Expand All @@ -11,44 +13,44 @@ namespace FluentLauncher.Infra.Settings.Converters;
/// Converts a string to a type T using JSON serialization
/// </summary>
/// <typeparam name="T">Type used in the application</typeparam>
public class JsonStringConverter<T> : IDataTypeConverter
public class JsonStringConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> : IDataTypeConverter<string, T?>
{
/// <inheritdoc/>
public Type SourceType => typeof(string);
private readonly JsonSerializerContext _serializerContext;

/// <inheritdoc/>
public Type TargetType => typeof(T);
public JsonStringConverter()
{
if (JsonStringConverterConfig.SerializerContext is null)
throw new InvalidOperationException("JsonSerializerContext is not available.");
_serializerContext = JsonStringConverterConfig.SerializerContext;
}

public static IDataTypeConverter<string, T?> Instance { get; } = new JsonStringConverter<T>();

/// <summary>
/// Returns the type used in the application
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public T? Convert(string json) => (T?)((IDataTypeConverter)this).Convert(json);

/// <inheritdoc/>
object? IDataTypeConverter.Convert(object? source)
public T? Convert(string json)
{
if (source is not string json)
return null;
if (json is null || JsonStringConverterConfig.SerializerContext is null)
return default;

return JsonSerializer.Deserialize(json, TargetType);
return (T?)JsonSerializer.Deserialize(json, typeof(T), _serializerContext);
}

/// <summary>
/// Returns the json string of an object
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
public string? Convert(T target) => (string?)((IDataTypeConverter)this).ConvertFrom(target);

/// <inheritdoc/>
object? IDataTypeConverter.ConvertFrom(object? target)
public string ConvertFrom(T? target)
{
if (target is not T)
return null;

return JsonSerializer.Serialize(target, TargetType);
return JsonSerializer.Serialize(target, typeof(T), _serializerContext);
}
}

public static class JsonStringConverterConfig
{
public static JsonSerializerContext? SerializerContext { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

</Project>
7 changes: 4 additions & 3 deletions infra/FluentLauncher.Infra.Settings/Interfaces.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
Expand Down Expand Up @@ -53,15 +54,15 @@ public interface ISettingsStorage
/// <exception cref="KeyNotFoundException">Throws if the key is not found</exception>
/// <remarks>If a type converter is specified, the storage provider will be responsible for converting the type stored
/// <br/> into T when the item is accessed, and converting T into the type used in storage.</remarks>
T GetValue<T>(string path) where T : notnull;
T GetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string path) where T : notnull;

/// <summary>
/// Obtains the value of a setting item from the storage
/// </summary>
/// <param name="path">A unique path that locates the item in the storage</param>
/// <param name="type">Type of the value expected<br/>This may be used when dealing with special types such as arrays.</param>
/// <returns></returns>
object GetValue(string path, Type type);
//object GetValue(string path, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type);

/// <summary>
/// Sets a setting item to a new value in the storage
Expand All @@ -78,5 +79,5 @@ public interface ISettingsStorage
/// <param name="value">The new value</param>
/// <param name="type">Type of the value</param>
/// <returns></returns>
void SetValue(string path, object value, Type type);
//void SetValue(string path, object value, Type type);
}

This file was deleted.

5 changes: 0 additions & 5 deletions infra/FluentLauncher.Infra.Settings/SettingsAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ namespace FluentLauncher.Infra.Settings
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class SettingItemAttribute : Attribute
{
/// <summary>
/// Key of the setting item
/// </summary>
[Obsolete]
public string? Key { get; init; }
/// <summary>
/// An optional converter to convert the value stored in the storage to the type of the property.<br/>If not specified, type casting will be used.
/// </summary>
Expand Down
Loading
Loading