Skip to content

Commit

Permalink
fix: Improve metadata update tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Oct 19, 2023
1 parent e2047fa commit bf5bb86
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,32 +121,47 @@ private string[] GetMetadataUpdateCapabilities()
#if __WASM__ || __SKIA__
private void AssemblyReload(AssemblyDeltaReload assemblyDeltaReload)
{
if (assemblyDeltaReload.IsValid())
try
{
if (this.Log().IsEnabled(LogLevel.Trace))
if (assemblyDeltaReload.IsValid())
{
this.Log().Trace($"Applying IL Delta after {assemblyDeltaReload.FilePath}, Guid:{assemblyDeltaReload.ModuleId}");
}
if (this.Log().IsEnabled(LogLevel.Trace))
{
this.Log().Trace($"Applying IL Delta after {assemblyDeltaReload.FilePath}, Guid:{assemblyDeltaReload.ModuleId}");
}

var changedTypesStreams = new MemoryStream(Convert.FromBase64String(assemblyDeltaReload.UpdatedTypes));
var changedTypesReader = new BinaryReader(changedTypesStreams);
var changedTypesStreams = new MemoryStream(Convert.FromBase64String(assemblyDeltaReload.UpdatedTypes));
var changedTypesReader = new BinaryReader(changedTypesStreams);

var delta = new UpdateDelta()
var delta = new UpdateDelta()
{
MetadataDelta = Convert.FromBase64String(assemblyDeltaReload.MetadataDelta),
ILDelta = Convert.FromBase64String(assemblyDeltaReload.ILDelta),
PdbBytes = Convert.FromBase64String(assemblyDeltaReload.PdbDelta),
ModuleId = Guid.Parse(assemblyDeltaReload.ModuleId),
UpdatedTypes = ReadIntArray(changedTypesReader)
};

_agent.ApplyDeltas(new[] { delta });

if (this.Log().IsEnabled(LogLevel.Trace))
{
this.Log().Trace($"Done applying IL Delta for {assemblyDeltaReload.FilePath}, Guid:{assemblyDeltaReload.ModuleId}");
}
}
else
{
MetadataDelta = Convert.FromBase64String(assemblyDeltaReload.MetadataDelta),
ILDelta = Convert.FromBase64String(assemblyDeltaReload.ILDelta),
PdbBytes = Convert.FromBase64String(assemblyDeltaReload.PdbDelta),
ModuleId = Guid.Parse(assemblyDeltaReload.ModuleId),
UpdatedTypes = ReadIntArray(changedTypesReader)
};

_agent.ApplyDeltas(new[] { delta });
if (this.Log().IsEnabled(LogLevel.Trace))
{
this.Log().Trace($"Failed to apply IL Delta for {assemblyDeltaReload.FilePath} ({assemblyDeltaReload})");
}
}
}
else
catch (Exception e)
{
if (this.Log().IsEnabled(LogLevel.Trace))
if (this.Log().IsEnabled(LogLevel.Error))
{
this.Log().Trace($"Failed to apply IL Delta for {assemblyDeltaReload.FilePath} ({assemblyDeltaReload})");
this.Log().Error($"Failed to process assembly reload {assemblyDeltaReload.FilePath}, Guid:{assemblyDeltaReload.ModuleId}: {e}");
}
}
}
Expand Down Expand Up @@ -196,6 +211,7 @@ private static async Task ReloadWithUpdatedTypes(Type[] updatedTypes)

try
{
UpdateGlobalResources(updatedTypes);

var handlerActions = _instance?.ElementAgent?.ElementHandlerActions;

Expand Down Expand Up @@ -301,6 +317,128 @@ private static async Task ReloadWithUpdatedTypes(Type[] updatedTypes)
}
}

/// <summary>
/// Updates App-level resources (from app.xaml) using the provided updated types list.
/// </summary>
private static void UpdateGlobalResources(Type[] updatedTypes)
{
var globalResourceTypes = updatedTypes
.Where(t => t?.FullName != null && (
t.FullName.EndsWith("GlobalStaticResources", StringComparison.OrdinalIgnoreCase)
|| t.FullName[..^2].EndsWith("GlobalStaticResources", StringComparison.OrdinalIgnoreCase)))
.ToArray();

if (globalResourceTypes.Length != 0)
{
if (_log.IsEnabled(LogLevel.Debug))
{
_log.Debug($"Updating app resources");
}

MethodInfo? GetInitMethod(Type type, string name)
{
if (type.GetMethod(
name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, Array.Empty<Type>()) is { } initializeMethod)
{
return initializeMethod;
}
else
{
if (_log.IsEnabled(LogLevel.Debug))
{
_log.Debug($"{name} method not found on {type}");
}

return null;
}
}

// First, register all dictionaries
foreach (var globalResourceType in globalResourceTypes)
{
// Follow the initialization sequence implemented by
// App.InitializeComponent (Initialize then RegisterResourceDictionariesBySourceLocal).

if (GetInitMethod(globalResourceType, "Initialize") is { } initializeMethod)
{
if (_log.IsEnabled(LogLevel.Trace))
{
_log.Debug($"Initializing resources for {globalResourceType}");
}

// Invoke initializers so default types and other resources get updated.
initializeMethod.Invoke(null, null);
}

if (GetInitMethod(globalResourceType, "RegisterResourceDictionariesBySourceLocal") is { } registerResourceDictionariesBySourceLocalMethod)
{
if (_log.IsEnabled(LogLevel.Trace))
{
_log.Debug($"Initializing resources sources for {globalResourceType}");
}

// Invoke initializers so default types and other resources get updated.
registerResourceDictionariesBySourceLocalMethod.Invoke(null, null);
}
}


// Then find over updated types to find the ones that are implementing IXamlResourceDictionaryProvider
List<Uri> updatedDictionaries = new();

foreach (var updatedType in updatedTypes)
{
if (updatedType.GetInterfaces().Contains(typeof(IXamlResourceDictionaryProvider)))
{
if (_log.IsEnabled(LogLevel.Trace))
{
_log.Debug($"Updating resources for {updatedType}");
}

// This assumes that we're using an explicit implementation of IXamlResourceDictionaryProvider, which
// provides an instance property that returns the new dictionary.
var staticDictionaryProperty = updatedType
.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic);

if (staticDictionaryProperty?.GetMethod is { } getMethod)
{
if (getMethod.Invoke(null, null) is IXamlResourceDictionaryProvider provider
&& provider.GetResourceDictionary() is { Source: not null } dictionary)
{
updatedDictionaries.Add(dictionary.Source);
}
}
}
}

// Traverse the current app's tree to replace dictionaries matching the source property
// with the updated ones.
UpdateResourceDictionaries(updatedDictionaries, Application.Current.Resources);

// Force the app reevaluate global resources changes
Application.Current.UpdateResourceBindingsForHotReload();
}
}

/// <summary>
/// Refreshes ResourceDictionary instances that have been detected as updated
/// </summary>
/// <param name="updatedDictionaries"></param>
/// <param name="root"></param>
private static void UpdateResourceDictionaries(List<Uri> updatedDictionaries, ResourceDictionary root)
{
var dictionariesToRefresh = root
.MergedDictionaries
.Where(merged => updatedDictionaries.Any(d => d == merged.Source))
.ToArray();

foreach (var merged in dictionariesToRefresh)
{
root.RefreshMergedDictionary(merged);
}
}

private static void ReplaceViewInstance(UIElement instance, Type replacementType, ElementUpdateAgent.ElementUpdateHandlerActions? handler = default, Type[]? updatedTypes = default)
{
if (replacementType.GetConstructor(Array.Empty<Type>()) is { } creator)
Expand Down
22 changes: 22 additions & 0 deletions src/Uno.UI/UI/Xaml/ResourceDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,28 @@ private bool ContainsKeyMerged(in ResourceKey resourceKey)
return false;
}

/// <summary>
/// Refreshes the provided dictionary with the latest version of the dictionary (during hot reload)
/// </summary>
/// <param name="merged">A dictionary present in the merged dictionaries</param>
internal void RefreshMergedDictionary(ResourceDictionary merged)
{
if (merged.Source is null)
{
throw new InvalidOperationException("Unable to refresh dictionary without a Source being set");
}

var index = _mergedDictionaries.IndexOf(merged);
if (index != -1)
{
_mergedDictionaries[index] = ResourceResolver.RetrieveDictionaryForSource(merged.Source);
}
else
{
throw new InvalidOperationException("The provided dictionary cannot be found in the merged list");
}
}

private ResourceDictionary _activeThemeDictionary;
private ResourceKey _activeTheme;

Expand Down

0 comments on commit bf5bb86

Please sign in to comment.