Skip to content

Commit

Permalink
feat(mvux): Add support for hot-reload in dynamic feed
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Sep 18, 2023
1 parent 068746e commit 2074c83
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@

namespace Uno.Extensions.Reactive.Core.HotReload;

/// <summary>
/// Service responsible to handle hot reload events for the MVUX framework.
/// </summary>
internal static class HotReloadService
{
private static ILogger _log = typeof(HotReloadService).Log();

public static event Action<Type[]>? ApplicationUpdated;

internal static void ClearCache(Type[]? types)
{
}
Expand All @@ -37,6 +42,8 @@ internal static void UpdateApplication(Type[]? types)
BindableViewModelBase.HotPatch(model.Bindable, originalType, type);
}
}

ApplicationUpdated?.Invoke(types);
}

// As the MetadataUpdateOriginalTypeAttribute might have been generated in the project, we have to use reflection instead of cannot use this:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.Extensions.Logging;
using Uno.Extensions.Reactive.Core.HotReload;
using Uno.Extensions.Reactive.Logging;

namespace Uno.Extensions.Reactive.Sources;

/// <summary>
/// A dependency that will trigger a feed execution when the application is updated (hot-reload).
/// </summary>
internal sealed class HotReloadDependency : IDependency
{
private static int _count;
private readonly int _id = Interlocked.Increment(ref _count);

public HotReloadDependency(FeedSession session)
{
var feedDelegate = session.CoreExecutionAction;
var feedDelegateType = feedDelegate.Method.DeclaringType;

if (this.Log().IsEnabled(LogLevel.Information))
{
this.Log().Info($"[DYNAMIC_RELOAD] #{_id} Dynamic feed listening for updates of delegate {feedDelegateType?.FullName}->{feedDelegate.Method.Name}");
}

HotReloadService.ApplicationUpdated += OnUpdate;
session.Token.Register(() => HotReloadService.ApplicationUpdated -= OnUpdate);

void OnUpdate(Type[] types)
{
if (types.Contains(feedDelegateType))
{
if (this.Log().IsEnabled(LogLevel.Information))
{
this.Log().Info($"[DYNAMIC_RELOAD] #{_id} Delegate {feedDelegateType?.FullName}->{feedDelegate.Method.Name} has been updated");
}

session.Execute(new ExecuteRequest(this, "Hot reloading the updated delegate"));
}
}
}

/// <inheritdoc />
public async ValueTask OnExecuting(FeedExecution execution, CancellationToken ct)
{
}

/// <inheritdoc />
public async ValueTask OnExecuted(FeedExecution execution, FeedExecutionResult result, CancellationToken ct)
{
}
}
7 changes: 7 additions & 0 deletions src/Uno.Extensions.Reactive/Sources/Dynamic/DynamicFeed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Uno.Extensions.Reactive.Config;
using Uno.Extensions.Reactive.Core;

namespace Uno.Extensions.Reactive.Sources;
Expand All @@ -25,6 +26,12 @@ public DynamicFeed(AsyncFunc<Option<T>> dataProvider)
public async IAsyncEnumerable<Message<T>> GetSource(SourceContext context, [EnumeratorCancellation] CancellationToken ct = default)
{
await using var session = new FeedSession<T>(this, context, _dataProvider, ct);

if (FeedConfiguration.EffectiveHotReload.HasFlag(HotReloadSupport.DynamicFeed))
{
session.EnableHotReload();
}

while (await session.MoveNextAsync())
{
yield return session.Current;
Expand Down
3 changes: 3 additions & 0 deletions src/Uno.Extensions.Reactive/Sources/Dynamic/FeedSession.T.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public FeedSession(DynamicFeed<TResult> feed, SourceContext context, AsyncFunc<O
private List<ExecuteRequest>? _pendingRequests;
private Execution? _currentExecution; // This the execution currently loading the data,

/// <inheritdoc />
internal override Delegate CoreExecutionAction => _mainAsyncAction;

/// <inheritdoc />
public override void Execute(ExecuteRequest request)
{
Expand Down
7 changes: 7 additions & 0 deletions src/Uno.Extensions.Reactive/Sources/Dynamic/FeedSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ private protected FeedSession(ISignal<IMessage> owningSignal, SourceContext cont
/// </summary>
protected bool IsDisposed => _isDisposed is not 0;

/// <summary>
/// Gets application's delegate executed by this session.
/// WARNING This should used only for debugging and hot-reload purposes!
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal abstract Delegate CoreExecutionAction { get; }

/// <summary>
/// Requests to start a new execution.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,42 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;

namespace Uno.Extensions.Reactive.Sources;

internal static class FeedSessionExtensions
{
/// <summary>
/// Gets or create an object that will be shared across all executions of the current session.
/// </summary>
/// <typeparam name="TKey">Type of the key</typeparam>
/// <typeparam name="TValue">Type of the shared object</typeparam>
/// <param name="session">The session to configure.</param>
/// <param name="key">The key that identifies the value. It has to be unique between all dependencies.</param>
/// <param name="factory">Factory to create the object if missing.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TValue GetShared<TKey, TValue>(this FeedSession session, TKey key, Func<FeedSession, TKey, TValue> factory)
where TKey : notnull
where TValue : notnull
=> session.GetShared(key, static (sess, k, f) => f(sess, k), factory);

/// <summary>
/// Gets or create an object that will be shared across all executions of the current session.
/// </summary>
/// <typeparam name="TValue">Type of the shared object</typeparam>
/// <param name="session">The session to configure.</param>
/// <param name="factory">Factory to create the object if missing.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TValue GetShared<TValue>(this FeedSession session, Func<FeedSession, TValue> factory)
where TValue : notnull
=> session.GetShared(typeof(TValue), static (sess, _, f) => f(sess), factory);

/// <summary>
/// Enables the refresh **for the whole session**.
/// </summary>
/// <param name="session">The session to configure.</param>
public static void EnableRefresh(this FeedSession session)
=> session.GetShared(nameof(RefreshDependency), static (sess, _, __) => new RefreshDependency(sess), Unit.Default).Enable();
=> session.GetShared(static sess => new RefreshDependency(sess)).Enable();

/// <summary>
/// Enables the refresh **for the whole session** using an external refresh signal.
Expand All @@ -19,4 +45,11 @@ public static void EnableRefresh(this FeedSession session)
/// <param name="signal">The external refresh signal.</param>
public static void EnableRefresh(this FeedSession session, ISignal signal)
=> session.GetShared((nameof(RefreshSignalDependency), signal), static (sess, _, sig) => new RefreshSignalDependency(sess, sig), signal);

/// <summary>
/// Enables the refresh **for the whole session** when the given hot-reload.
/// </summary>
/// <param name="session"></param>
public static void EnableHotReload(this FeedSession session)
=> session.GetShared(static sess => new HotReloadDependency(sess));
}

0 comments on commit 2074c83

Please sign in to comment.