Skip to content

Commit 56e5199

Browse files
jjw24TBM13
authored andcommitted
Merge pull request Flow-Launcher#3854 from Flow-Launcher/plugin_initialization
Asynchronous Loading & Initialization Plugin Model to Improve Window Startup Speed
1 parent 8fdbae6 commit 56e5199

File tree

13 files changed

+464
-174
lines changed

13 files changed

+464
-174
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Flow.Launcher.Plugin;
2+
3+
namespace Flow.Launcher.Core.Plugin;
4+
5+
public interface IResultUpdateRegister
6+
{
7+
/// <summary>
8+
/// Register a plugin to receive results updated event.
9+
/// </summary>
10+
/// <param name="pair"></param>
11+
void RegisterResultsUpdatedEvent(PluginPair pair);
12+
}

Flow.Launcher.Core/Plugin/PluginManager.cs

Lines changed: 306 additions & 88 deletions
Large diffs are not rendered by default.

Flow.Launcher.Core/Plugin/PluginsLoader.cs

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSe
2828
return plugins;
2929
}
3030

31-
private static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source)
31+
private static List<PluginPair> DotNetPlugins(List<PluginMetadata> source)
3232
{
3333
var erroredPlugins = new List<string>();
3434

@@ -38,55 +38,57 @@ private static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source
3838
foreach (var metadata in metadatas)
3939
{
4040
var milliseconds = PublicApi.Instance.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () =>
41-
{
42-
Assembly assembly = null;
43-
IAsyncPlugin plugin = null;
41+
{
42+
Assembly assembly = null;
43+
IAsyncPlugin plugin = null;
4444

45-
try
46-
{
47-
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
48-
assembly = assemblyLoader.LoadAssemblyAndDependencies();
45+
try
46+
{
47+
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
48+
assembly = assemblyLoader.LoadAssemblyAndDependencies();
4949

50-
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly,
51-
typeof(IAsyncPlugin));
50+
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly,
51+
typeof(IAsyncPlugin));
5252

53-
plugin = Activator.CreateInstance(type) as IAsyncPlugin;
53+
plugin = Activator.CreateInstance(type) as IAsyncPlugin;
5454

55-
metadata.AssemblyName = assembly.GetName().Name;
56-
}
55+
metadata.AssemblyName = assembly.GetName().Name;
56+
}
5757
#if DEBUG
58-
catch (Exception)
59-
{
60-
throw;
61-
}
58+
catch (Exception)
59+
{
60+
throw;
61+
}
6262
#else
63-
catch (Exception e) when (assembly == null)
64-
{
65-
PublicApi.Instance.LogException(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
66-
}
67-
catch (InvalidOperationException e)
68-
{
69-
PublicApi.Instance.LogException(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
70-
}
71-
catch (ReflectionTypeLoadException e)
72-
{
73-
PublicApi.Instance.LogException(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
74-
}
75-
catch (Exception e)
76-
{
77-
PublicApi.Instance.LogException(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
78-
}
63+
catch (Exception e) when (assembly == null)
64+
{
65+
PublicApi.Instance.LogException(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
66+
}
67+
catch (InvalidOperationException e)
68+
{
69+
PublicApi.Instance.LogException(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
70+
}
71+
catch (ReflectionTypeLoadException e)
72+
{
73+
PublicApi.Instance.LogException(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
74+
}
75+
catch (Exception e)
76+
{
77+
PublicApi.Instance.LogException(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
78+
}
7979
#endif
8080

81-
if (plugin == null)
82-
{
83-
erroredPlugins.Add(metadata.Name);
84-
return;
85-
}
81+
if (plugin == null)
82+
{
83+
erroredPlugins.Add(metadata.Name);
84+
return;
85+
}
86+
87+
plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata });
88+
});
8689

87-
plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata });
88-
});
8990
metadata.InitTime += milliseconds;
91+
PublicApi.Instance.LogDebug(ClassName, $"Constructor cost for <{metadata.Name}> is <{metadata.InitTime}ms>");
9092
}
9193

9294
if (erroredPlugins.Count > 0)

Flow.Launcher.Core/Resource/Internationalization.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,22 @@ public static void UpdatePluginMetadataTranslations()
351351
}
352352
}
353353

354+
public static void UpdatePluginMetadataTranslation(PluginPair p)
355+
{
356+
// Update plugin metadata name & description
357+
if (p.Plugin is not IPluginI18n pluginI18N) return;
358+
try
359+
{
360+
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
361+
p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription();
362+
pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture);
363+
}
364+
catch (Exception e)
365+
{
366+
PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
367+
}
368+
}
369+
354370
#endregion
355371

356372
#region IDisposable

Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,21 @@ public interface IPublicAPI
168168
/// <summary>
169169
/// Get all loaded plugins
170170
/// </summary>
171+
/// <remarks>
172+
/// Will also return any plugins not fully initialized yet
173+
/// </remarks>
171174
/// <returns></returns>
172175
List<PluginPair> GetAllPlugins();
173176

177+
/// <summary>
178+
/// Get all initialized plugins
179+
/// </summary>
180+
/// <param name="includeFailed">
181+
/// Whether to include plugins that failed to initialize
182+
/// </param>
183+
/// <returns></returns>
184+
List<PluginPair> GetAllInitializedPlugins(bool includeFailed);
185+
174186
/// <summary>
175187
/// Registers a callback function for global keyboard events.
176188
/// </summary>

Flow.Launcher/App.xaml.cs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
167167
// So set to OnExplicitShutdown to prevent the application from shutting down before main window is created
168168
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
169169

170+
// Initialize notification system before any notification api is called
170171
Notification.Install();
171172

172173
// Enable Win32 dark mode if the system is in dark mode before creating all windows
@@ -182,19 +183,7 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
182183
RegisterDispatcherUnhandledException();
183184
RegisterTaskSchedulerUnhandledException();
184185

185-
var imageLoadertask = ImageLoader.InitializeAsync();
186-
187-
PluginManager.LoadPlugins(_settings.PluginSettings);
188-
189-
// Register ResultsUpdated event after all plugins are loaded
190-
Ioc.Default.GetRequiredService<MainViewModel>().RegisterResultsUpdatedEvent();
191-
192-
await PluginManager.InitializePluginsAsync();
193-
194-
// Update plugin titles after plugins are initialized with their api instances
195-
Internationalization.UpdatePluginMetadataTranslations();
196-
197-
await imageLoadertask;
186+
await ImageLoader.InitializeAsync();
198187

199188
_mainWindow = new MainWindow();
200189

@@ -213,7 +202,28 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
213202
RegisterExitEvents();
214203

215204
API.SaveAppAllSettings();
216-
API.LogInfo(ClassName, "End Flow Launcher startup ----------------------------------------------------");
205+
API.LogInfo(ClassName, "End Flow Launcher startup ------------------------------------------------------");
206+
207+
_ = API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
208+
{
209+
API.LogInfo(ClassName, "Begin plugin initialization ----------------------------------------------------");
210+
211+
PluginManager.LoadPlugins(_settings.PluginSettings);
212+
213+
await PluginManager.InitializePluginsAsync(_mainVM);
214+
215+
// Refresh home page after plugins are initialized because users may open main window during plugin initialization
216+
// And home page is created without full plugin list
217+
if (_settings.ShowHomePage && _mainVM.QueryResultsSelected() && string.IsNullOrEmpty(_mainVM.QueryText))
218+
{
219+
_mainVM.QueryResults();
220+
}
221+
222+
// Save all settings since we possibly update the plugin environment paths
223+
API.SaveAppAllSettings();
224+
225+
API.LogInfo(ClassName, "End plugin initialization ------------------------------------------------------");
226+
});
217227
});
218228
}
219229

Flow.Launcher/Helper/ResultHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static class ResultHelper
2020
{
2121
var plugin = PluginManager.GetPluginForId(pluginId);
2222
if (plugin == null) return null;
23-
var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins);
23+
var query = QueryBuilder.Build(rawQuery, PluginManager.GetNonGlobalPlugins());
2424
if (query == null) return null;
2525
try
2626
{

Flow.Launcher/Languages/en.xaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
<system:String x:Key="PositionReset">Position Reset</system:String>
6363
<system:String x:Key="PositionResetToolTip">Reset search window position</system:String>
6464
<system:String x:Key="queryTextBoxPlaceholder">Type here to search</system:String>
65+
<system:String x:Key="pluginStillInitializing">{0}: This plugin is still initializing...</system:String>
66+
<system:String x:Key="pluginStillInitializingSubtitle">Select this result to requery</system:String>
67+
<system:String x:Key="pluginFailedToRespond">{0}: Failed to respond!</system:String>
68+
<system:String x:Key="pluginFailedToRespondSubtitle">Select this result for more info</system:String>
6569

6670
<!-- Setting General -->
6771
<system:String x:Key="flowlauncher_settings">Settings</system:String>

Flow.Launcher/MainWindow.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ private void OnKeyDown(object sender, KeyEventArgs e)
331331
&& QueryTextBox.CaretIndex == QueryTextBox.Text.Length)
332332
{
333333
var queryWithoutActionKeyword =
334-
QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search;
334+
QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.GetNonGlobalPlugins())?.Search;
335335

336336
if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword))
337337
{

Flow.Launcher/PublicAPIInstance.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.ComponentModel;
66
using System.Diagnostics;
77
using System.IO;
8-
using System.Linq;
98
using System.Net;
109
using System.Runtime.CompilerServices;
1110
using System.Runtime.InteropServices;
@@ -229,7 +228,10 @@ private static async Task<Exception> RetryActionOnSTAThreadAsync(Action action,
229228

230229
public string GetTranslation(string key) => Internationalization.GetTranslation(key);
231230

232-
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
231+
public List<PluginPair> GetAllPlugins() => PluginManager.GetAllLoadedPlugins();
232+
233+
public List<PluginPair> GetAllInitializedPlugins(bool includeFailed) =>
234+
PluginManager.GetAllInitializedPlugins(includeFailed);
233235

234236
public MatchResult FuzzySearch(string query, string stringToCompare) =>
235237
StringMatcher.FuzzySearch(query, stringToCompare);

0 commit comments

Comments
 (0)