66using System ;
77using System . Collections . Generic ;
88using System . Collections . Immutable ;
9+ using System . Diagnostics ;
910using System . IO ;
1011using System . Linq ;
1112using System . Runtime . InteropServices ;
1617
1718namespace Microsoft . DotNet . HotReload ;
1819
19- internal sealed class HotReloadClients ( ImmutableArray < ( HotReloadClient client , string name ) > clients , AbstractBrowserRefreshServer ? browserRefreshServer ) : IDisposable
20+ /// <summary>
21+ /// Facilitates Hot Reload updates across multiple clients/processes.
22+ /// </summary>
23+ /// <param name="clients">
24+ /// Clients that handle managed updates and static asset updates if <paramref name="useRefreshServerToApplyStaticAssets"/> is false.
25+ /// </param>
26+ /// <param name="browserRefreshServer">
27+ /// Browser refresh server used to communicate managed code update status and errors to the browser,
28+ /// and to apply static asset updates if <paramref name="useRefreshServerToApplyStaticAssets"/> is true.
29+ /// </param>
30+ /// <param name="useRefreshServerToApplyStaticAssets">
31+ /// True to use <paramref name="browserRefreshServer"/> to apply static asset updates (if available).
32+ /// False to use the <paramref name="clients"/> to apply static asset updates.
33+ /// </param>
34+ internal sealed class HotReloadClients (
35+ ImmutableArray < ( HotReloadClient client , string name ) > clients ,
36+ AbstractBrowserRefreshServer ? browserRefreshServer ,
37+ bool useRefreshServerToApplyStaticAssets ) : IDisposable
2038{
21- public HotReloadClients ( HotReloadClient client , AbstractBrowserRefreshServer ? browserRefreshServer )
22- : this ( [ ( client , "" ) ] , browserRefreshServer )
23- {
24- }
25-
2639 /// <summary>
2740 /// Disposes all clients. Can occur unexpectedly whenever the process exits.
2841 /// </summary>
@@ -34,6 +47,16 @@ public void Dispose()
3447 }
3548 }
3649
50+ /// <summary>
51+ /// True if Hot Reload is implemented via managed agents.
52+ /// The update itself might not be managed code update, it may be a static asset update implemented via a managed agent.
53+ /// </summary>
54+ public bool IsManagedAgentSupported
55+ => ! clients . IsEmpty ;
56+
57+ public bool UseRefreshServerToApplyStaticAssets
58+ => useRefreshServerToApplyStaticAssets ;
59+
3760 public AbstractBrowserRefreshServer ? BrowserRefreshServer
3861 => browserRefreshServer ;
3962
@@ -59,18 +82,6 @@ public event Action<int, string> OnRuntimeRudeEdit
5982 }
6083 }
6184
62- /// <summary>
63- /// All clients share the same loggers.
64- /// </summary>
65- public ILogger ClientLogger
66- => clients . First ( ) . client . Logger ;
67-
68- /// <summary>
69- /// All clients share the same loggers.
70- /// </summary>
71- public ILogger AgentLogger
72- => clients . First ( ) . client . AgentLogger ;
73-
7485 internal void ConfigureLaunchEnvironment ( IDictionary < string , string > environmentBuilder )
7586 {
7687 foreach ( var ( client , _) in clients )
@@ -99,6 +110,12 @@ internal async ValueTask WaitForConnectionEstablishedAsync(CancellationToken can
99110 /// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
100111 public async ValueTask < ImmutableArray < string > > GetUpdateCapabilitiesAsync ( CancellationToken cancellationToken )
101112 {
113+ if ( ! IsManagedAgentSupported )
114+ {
115+ // empty capabilities will cause rude edit ENC0097: NotSupportedByRuntime.
116+ return [ ] ;
117+ }
118+
102119 if ( clients is [ var ( singleClient , _) ] )
103120 {
104121 return await singleClient . GetUpdateCapabilitiesAsync ( cancellationToken ) ;
@@ -114,6 +131,9 @@ public async ValueTask<ImmutableArray<string>> GetUpdateCapabilitiesAsync(Cancel
114131 /// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
115132 public async Task < Task > ApplyManagedCodeUpdatesAsync ( ImmutableArray < HotReloadManagedCodeUpdate > updates , CancellationToken applyOperationCancellationToken , CancellationToken cancellationToken )
116133 {
134+ // shouldn't be called if there are no clients
135+ Debug . Assert ( IsManagedAgentSupported ) ;
136+
117137 // Apply to all processes.
118138 // The module the change is for does not need to be loaded to any of the processes, yet we still consider it successful if the application does not fail.
119139 // In each process we store the deltas for application when/if the module is loaded to the process later.
@@ -137,6 +157,9 @@ async Task CompleteApplyOperationAsync()
137157 /// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
138158 public async ValueTask InitialUpdatesAppliedAsync ( CancellationToken cancellationToken )
139159 {
160+ // shouldn't be called if there are no clients
161+ Debug . Assert ( IsManagedAgentSupported ) ;
162+
140163 if ( clients is [ var ( singleClient , _) ] )
141164 {
142165 await singleClient . InitialUpdatesAppliedAsync ( cancellationToken ) ;
@@ -150,23 +173,26 @@ public async ValueTask InitialUpdatesAppliedAsync(CancellationToken cancellation
150173 /// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
151174 public async Task < Task > ApplyStaticAssetUpdatesAsync ( IEnumerable < StaticWebAsset > assets , CancellationToken applyOperationCancellationToken , CancellationToken cancellationToken )
152175 {
153- if ( browserRefreshServer != null )
176+ if ( useRefreshServerToApplyStaticAssets )
154177 {
178+ Debug . Assert ( browserRefreshServer != null ) ;
155179 return browserRefreshServer . UpdateStaticAssetsAsync ( assets . Select ( static a => a . RelativeUrl ) , applyOperationCancellationToken ) . AsTask ( ) ;
156180 }
157181
182+ // shouldn't be called if there are no clients
183+ Debug . Assert ( IsManagedAgentSupported ) ;
184+
158185 var updates = new List < HotReloadStaticAssetUpdate > ( ) ;
159186
160187 foreach ( var asset in assets )
161188 {
162189 try
163190 {
164- ClientLogger . LogDebug ( "Loading asset '{Url}' from '{Path}'." , asset . RelativeUrl , asset . FilePath ) ;
165191 updates . Add ( await HotReloadStaticAssetUpdate . CreateAsync ( asset , cancellationToken ) ) ;
166192 }
167193 catch ( Exception e ) when ( e is not OperationCanceledException )
168194 {
169- ClientLogger . LogError ( "Failed to read file {FilePath}: {Message}" , asset . FilePath , e . Message ) ;
195+ clients . First ( ) . client . Logger . LogError ( "Failed to read file {FilePath}: {Message}" , asset . FilePath , e . Message ) ;
170196 continue ;
171197 }
172198 }
@@ -177,6 +203,10 @@ public async Task<Task> ApplyStaticAssetUpdatesAsync(IEnumerable<StaticWebAsset>
177203 /// <param name="cancellationToken">Cancellation token. The cancellation should trigger on process terminatation.</param>
178204 public async ValueTask < Task > ApplyStaticAssetUpdatesAsync ( ImmutableArray < HotReloadStaticAssetUpdate > updates , CancellationToken applyOperationCancellationToken , CancellationToken cancellationToken )
179205 {
206+ // shouldn't be called if there are no clients
207+ Debug . Assert ( IsManagedAgentSupported ) ;
208+ Debug . Assert ( ! useRefreshServerToApplyStaticAssets ) ;
209+
180210 var applyTasks = await Task . WhenAll ( clients . Select ( c => c . client . ApplyStaticAssetUpdatesAsync ( updates , applyOperationCancellationToken , cancellationToken ) ) ) ;
181211
182212 return Task . WhenAll ( applyTasks ) ;
0 commit comments