Skip to content

Commit

Permalink
feat: Adding reloadcompleted callback
Browse files Browse the repository at this point in the history
  • Loading branch information
nickrandolph committed Dec 13, 2023
1 parent 3784d8a commit 4168502
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 8 deletions.
2 changes: 2 additions & 0 deletions doc/articles/uno-development/uno-internals-hotreload.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ static void BeforeVisualTreeUpdate(Type[]? updatedTypes);

static void AfterVisualTreeUpdate(Type[]? updatedTypes);

static void ReloadCompleted(Type[]? updatedTypes, bool uiUpdated);

static void ElementUpdate(FrameworkElement, Type[]?);

static void BeforeElementReplaced(FrameworkElement, FrameworkElement, Type[]?);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ private static async Task<bool> ShouldReload()
}
try
{
return await TypeMappings.WaitForResume();
var waiter = TypeMappings.WaitForResume();
if (!waiter.IsCompleted)
{
return false;
}
return await waiter;
}
finally
{
Expand All @@ -69,16 +74,18 @@ private static async Task<bool> ShouldReload()

private static async Task ReloadWithUpdatedTypes(Type[] updatedTypes)
{
if (!await ShouldReload())
{
return;
}
var handlerActions = ElementAgent?.ElementHandlerActions;

var uiUpdating = true;
try
{
UpdateGlobalResources(updatedTypes);
if (!await ShouldReload())
{
uiUpdating = false;
return;
}

var handlerActions = ElementAgent?.ElementHandlerActions;
UpdateGlobalResources(updatedTypes);

// Action: BeforeVisualTreeUpdate
// This is called before the visual tree is updated
Expand Down Expand Up @@ -178,8 +185,14 @@ private static async Task ReloadWithUpdatedTypes(Type[] updatedTypes)
{
_log.Error($"Error doing UI Update - {ex.Message}", ex);
}
uiUpdating = false;
throw;
}
finally
{
// Action: ReloadCompleted
_ = handlerActions?.Do(h => h.Value.ReloadCompleted(updatedTypes, uiUpdating)).ToArray();
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ internal sealed class ElementUpdateHandlerActions
/// <summary>
/// This will get invoked whenever UpdateApplication is invoked
/// but before any updates are applied to the visual tree.
/// This is only invoked if
/// a) there's no other update in progress
/// b) ui updating hasn't been paused
/// This is only invoked once per UpdateApplication,
/// irrespective of the number of types the handler is registered for
/// </summary>
Expand All @@ -60,11 +63,26 @@ internal sealed class ElementUpdateHandlerActions
/// <summary>
/// This will get invoked whenever UpdateApplication is invoked
/// after all updates have been applied to the visual tree.
/// This is only invoked if
/// a) there's no other update in progress
/// b) ui updating hasn't been paused
/// This is only invoked once per UpdateApplication,
/// irrespective of the number of types the handler is registered for
/// </summary>
public Action<Type[]?> AfterVisualTreeUpdate { get; set; } = _ => { };

/// <summary>
/// This will get invoked whenever UpdateApplication is invoked
/// after all updates have been applied to the visual tree.
/// This is always invoked, even if
/// a) there's another update in progress
/// b) ui updating is paused
/// This is only invoked once per UpdateApplication,
/// irrespective of the number of types the handler is registered for
/// Second parameter (bool) indicates whether the ui was updated or not
/// </summary>
public Action<Type[]?, bool> ReloadCompleted { get; set; } = (_, _) => { };

/// <summary>
/// This is invoked when a specific element is found in the tree.
/// This would be useful if the element holds references to controls
Expand Down Expand Up @@ -178,6 +196,13 @@ internal void GetElementHandlerActions(
methodFound = true;
}

if (GetHandlerMethod(handlerType, nameof(ElementUpdateHandlerActions.ReloadCompleted), new[] { typeof(Type[]), typeof(bool) }) is MethodInfo reloadCompleted)
{
_log($"Adding handler for {nameof(updateActions.ReloadCompleted)} for {handlerType}");
updateActions.ReloadCompleted = CreateHandlerAction<Action<Type[]?, bool>>(reloadCompleted);
methodFound = true;
}

if (GetHandlerMethod(handlerType, nameof(ElementUpdateHandlerActions.ElementUpdate), new[] { typeof(FrameworkElement), typeof(Type[]) }) is MethodInfo elementUpdate)
{
_log($"Adding handler for {nameof(updateActions.ElementUpdate)} for {handlerType}");
Expand Down Expand Up @@ -229,7 +254,7 @@ internal void GetElementHandlerActions(
if (!methodFound)
{
_log($"No invokable methods found on metadata handler type '{handlerType}'. " +
$"Allowed methods are BeforeVisualTreeUpdate, AfterVisualTreeUpdate, ElementUpdate, BeforeElementReplaced, AfterElementReplaced, CaptureState, RestoreState");
$"Allowed methods are BeforeVisualTreeUpdate, AfterVisualTreeUpdate, ElementUpdate, BeforeElementReplaced, AfterElementReplaced, CaptureState, RestoreState, ReloadCompleted");
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,66 @@ await HotReloadHelper.UpdateServerFileAndRevert<HR_Frame_Pages_Page1>(
}


/// <summary>
/// Checks that a simple change to a XAML element (change Text on TextBlock) will not be applied to
/// the currently visible page when HR is paused and that the change will be applied once HR is resumed
/// Open Page1
/// Pause HR
/// Change Page1 (no changes to Page1 UI)
/// Resume HR (changes applied to Page1 UI)
/// </summary>
[TestMethod]
public async Task Check_Can_Change_Page1_Pause_ReloadCompleted_HR()
{
var ct = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;

var frame = new Windows.UI.Xaml.Controls.Frame();
UnitTestsUIContentHelper.Content = frame;

frame.Navigate(typeof(HR_Frame_Pages_Page1));

// Check the initial text of the TextBlock
await frame.ValidateTextOnChildTextBlock(FirstPageTextBlockOriginalText);

// Pause HR
TypeMappings.Pause();
try
{
var waitingTask = TestingUpdateHandler.WaitForReloadCompleted();

// Check the text of the TextBlock is the same even after a HR change (since HR is paused)
await HotReloadHelper.UpdateServerFileAndRevert<HR_Frame_Pages_Page1>(
FirstPageTextBlockOriginalText,
FirstPageTextBlockChangedText,
async () =>
{
// Confirm that reload compeleted has fired
var uiUpdated = await waitingTask.WaitAsync(ct);
Assert.IsFalse(uiUpdated, "UI should not have updated whilst ui updates paused");
await frame.ValidateTextOnChildTextBlock(FirstPageTextBlockOriginalText);
},
ct);
}
finally
{
// Resume HR
TypeMappings.Resume(false);
}

// Although HR has been un-paused (resumed) the UI should not have updated at this point
// due to false parameter passed to Resume method
await frame.ValidateTextOnChildTextBlock(FirstPageTextBlockOriginalText);

// Force a refresh
Window.Current.ForceHotReloadUpdate();

await TestingUpdateHandler.WaitForVisualTreeUpdate().WaitAsync(ct);

// Check that the text has been updated
await frame.ValidateTextOnChildTextBlock(FirstPageTextBlockOriginalText);
}


/// <summary>
/// Checks that a simple xaml change to the current page will be retained when
/// navigating forward to a new page and then going back to the original page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ namespace Uno.UI.RuntimeTests.Tests.HotReload.Frame.HRApp.Tests;
public static class TestingUpdateHandler
{
private static TaskCompletionSource _visualTreeUpdateCompletion = new TaskCompletionSource();
private static TaskCompletionSource<bool> _reloadCompletion = new TaskCompletionSource<bool>();

/// <summary>
/// This method is invoked whenever the UI is updated after a Hot Reload operation
/// Only if ui updating hasn't be paused
/// </summary>
/// <param name="updatedTypes"></param>
public static void AfterVisualTreeUpdate(Type[]? updatedTypes)
Expand All @@ -21,6 +23,19 @@ public static void AfterVisualTreeUpdate(Type[]? updatedTypes)
oldCompletion.TrySetResult();
}

/// <summary>
/// This method is invoked whenever the UI is updated after a Hot Reload operation
/// </summary>
/// <param name="updatedTypes">The types that are updated</param>
/// <param name="uiUpdated">Whether or not the UI was updated</param>
public static void ReloadCompleted(Type[]? updatedTypes, bool uiUpdated)
{
var oldCompletion = _reloadCompletion;
_reloadCompletion = new TaskCompletionSource<bool>();
oldCompletion.TrySetResult(uiUpdated);
}


/// <summary>
/// Gets a task that allows the called to wait for an UI Update to
/// complete. This can block indefinitely if type mappings has been paused
Expand All @@ -31,4 +46,16 @@ public static async Task WaitForVisualTreeUpdate()
{
await _visualTreeUpdateCompletion.Task;
}


/// <summary>
/// Gets a task that allows the called to wait for an UI Update to
/// complete. This should not block indefinitely even if type mappings has been paused
/// see TypeMappings.Pause()
/// </summary>
/// <returns></returns>
public static async Task<bool> WaitForReloadCompleted()
{
return await _reloadCompletion.Task;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<_UnoBaseReferenceBinPath Condition="exists('$(SamplesAppArtifactPath)')">$(SamplesAppArtifactPath)</_UnoBaseReferenceBinPath>

<UnoUIRuntimeTestEngine_DisableValidateUno>true</UnoUIRuntimeTestEngine_DisableValidateUno>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

<Target Name="DisplayBasePath" BeforeTargets="BeforeBuild">
Expand Down

0 comments on commit 4168502

Please sign in to comment.