Skip to content

Commit

Permalink
feat(shell):Integrate Module Loader into the shell
Browse files Browse the repository at this point in the history
  • Loading branch information
BalassaMarton committed Oct 19, 2023
1 parent caa3565 commit e12ce59
Show file tree
Hide file tree
Showing 37 changed files with 876 additions and 281 deletions.
13 changes: 7 additions & 6 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project>
<ItemGroup>
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.0" />
<PackageVersion Include="CommunityToolkit.HighPerformance" Version="8.2.1" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.0" />
<PackageVersion Include="CommunityToolkit.HighPerformance" Version="8.2.1" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="FluentAssertions.Json" Version="6.1.0" />
<PackageVersion Include="Google.Protobuf" Version="3.23.4" />
<PackageVersion Include="Grpc.AspNetCore.Server" Version="2.53.0" />
Expand All @@ -13,7 +14,7 @@
<PackageVersion Include="Grpc.Tools" Version="2.54.0" />
<PackageVersion Include="Grpc" Version="2.46.6" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.4.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
Expand All @@ -25,7 +26,7 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2045.28" />
<PackageVersion Include="Moq.Contrib.HttpClient" Version="1.4.0" />
<PackageVersion Include="Moq" Version="4.20.69" />
Expand Down
14 changes: 14 additions & 0 deletions src/messaging/dotnet/src/Client/Client/MessageRouterClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// and limitations under the License.

using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading.Channels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
Expand Down Expand Up @@ -436,6 +437,8 @@ private async Task<TResponse> SendRequestAsync<TResponse>(
CancellationToken cancellationToken)
where TResponse : AbstractResponse
{
CheckNotOnMainThread();

var tcs = new TaskCompletionSource<AbstractResponse>(TaskCreationOptions.RunContinuationsAsynchronously);

if (!_pendingRequests.TryAdd(request.RequestId, tcs))
Expand Down Expand Up @@ -727,6 +730,17 @@ private ValueTask TryUnsubscribe(Topic topic)
: default;
}

[DebuggerStepThrough]
private static void CheckNotOnMainThread()
{
#if DEBUG
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
throw new InvalidOperationException("The current thread is the main thread. Awaiting the resulting Task can cause a deadlock.");
}
#endif
}

private enum ConnectionState
{
NotConnected,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

namespace MorganStanley.ComposeUI.Messaging.Server;

// TODO: Also implement IMessageRouter to speed up in-process messaging
internal class MessageRouterServer : IMessageRouterServer
{
public MessageRouterServer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface IModuleRunner
{
string ModuleType { get; }

Task Start(IModuleInstance moduleInstance, StartupContext startupContext, Func<Task> pipeline);
Task Start(StartupContext startupContext, Func<Task> pipeline);

Task Stop(IModuleInstance moduleInstance);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Morgan Stanley makes this available to you under the Apache License,
// Version 2.0 (the "License"). You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0.
//
// See the NOTICE file distributed with this work for additional information
// regarding copyright ownership. Unless required by applicable law or agreed
// to in writing, software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions
// and limitations under the License.

namespace MorganStanley.ComposeUI.ModuleLoader;

public static class ModuleLoaderConstants
{
public static readonly Uri DefaultUri = new("about:blank");
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ public sealed class StartupContext
private readonly object _lock = new();
private readonly List<object> _properties = new();

public StartupContext(StartRequest startRequest)
public StartupContext(StartRequest startRequest, IModuleInstance moduleInstance)
{
StartRequest = startRequest;
ModuleInstance = moduleInstance;
}

public StartRequest StartRequest { get; }

public IModuleInstance ModuleInstance { get; }

public void AddProperty<T>(T value)
{
ArgumentNullException.ThrowIfNull(value, nameof(value));
Expand All @@ -42,3 +45,34 @@ public IEnumerable<object> GetProperties()
}
}
}

public static class StartupContextExtensions
{
public static IEnumerable<T> GetProperties<T>(this StartupContext startupContext)
{
return startupContext.GetProperties().OfType<T>();

Check warning on line 53 in src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs

View check run for this annotation

Codecov / codecov/patch

src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs#L53

Added line #L53 was not covered by tests
}

public static T GetOrAddProperty<T>(this StartupContext startupContext, Func<StartupContext, T> newValueFactory)
{
var property = startupContext.GetProperties<T>().FirstOrDefault();

Check warning on line 58 in src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs

View check run for this annotation

Codecov / codecov/patch

src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs#L58

Added line #L58 was not covered by tests

if (property == null)

Check warning on line 60 in src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs

View check run for this annotation

Codecov / codecov/patch

src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs#L60

Added line #L60 was not covered by tests
{
property = newValueFactory(startupContext);
startupContext.AddProperty(property);

Check warning on line 63 in src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs

View check run for this annotation

Codecov / codecov/patch

src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs#L62-L63

Added lines #L62 - L63 were not covered by tests
}

return property;

Check warning on line 66 in src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs

View check run for this annotation

Codecov / codecov/patch

src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs#L66

Added line #L66 was not covered by tests
}

public static T GetOrAddProperty<T>(this StartupContext startupContext, Func<T> newValueFactory)
{
return GetOrAddProperty<T>(startupContext, _ => newValueFactory());

Check warning on line 71 in src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs

View check run for this annotation

Codecov / codecov/patch

src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs#L71

Added line #L71 was not covered by tests
}

public static T GetOrAddProperty<T>(this StartupContext startupContext) where T : class, new()
{
return GetOrAddProperty<T>(startupContext, _ => new T());

Check warning on line 76 in src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs

View check run for this annotation

Codecov / codecov/patch

src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader.Abstractions/StartupContext.cs#L76

Added line #L76 was not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public sealed class WebManifestDetails
/// <summary>
/// The URL to open when this module is started.
/// </summary>
public Uri Url { get; init; }
public Uri Url { get; init; } = ModuleLoaderConstants.DefaultUri;

/// <summary>
/// The URL of the window icon, if any.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ namespace MorganStanley.ComposeUI.ModuleLoader;
/// </summary>
public sealed class WebStartupProperties
{
public Uri Url { get; set; }
public Uri Url { get; set; } = ModuleLoaderConstants.DefaultUri;
public Uri? IconUrl { get; set; }
}
public List<WebModuleScriptProvider> ScriptProviders { get; } = new();
}

public delegate ValueTask<string> WebModuleScriptProvider(IModuleInstance moduleInstance);
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using MorganStanley.ComposeUI.ModuleLoader;
using MorganStanley.ComposeUI.ModuleLoader.Runners;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async Task<IModuleInstance> StartModule(StartRequest request)
throw new Exception($"Unknown Module id: {request.ModuleId}");
}

if (!_moduleRunners.TryGetValue(request.ModuleId, out var moduleRunner))
if (!_moduleRunners.TryGetValue(manifest.ModuleType, out var moduleRunner))
{
throw new Exception($"No module runner available for {manifest.ModuleType} module type");
}
Expand All @@ -57,15 +57,15 @@ public async Task<IModuleInstance> StartModule(StartRequest request)
_modules.TryAdd(instanceId, moduleInstance);

_lifetimeEvents.OnNext(new LifetimeEvent.Starting(moduleInstance));
var startupContext = new StartupContext(request);
var startupContext = new StartupContext(request, moduleInstance);

var pipeline = _startupActions
.Reverse()
.Aggregate(
() => Task.CompletedTask,
(next, action) => () => action.InvokeAsync(startupContext, next));

await moduleRunner.Start(moduleInstance, startupContext, pipeline);
await moduleRunner.Start(startupContext, pipeline);
moduleInstance.AddProperties(startupContext.GetProperties());
_lifetimeEvents.OnNext(new LifetimeEvent.Started(moduleInstance));

Expand All @@ -84,11 +84,12 @@ public async Task StopModule(StopRequest request)

public async ValueTask DisposeAsync()
{
_lifetimeEvents.Dispose();
foreach (var module in _modules.Values)
{
await StopModuleInternal(module);
}
_modules.Clear();
_lifetimeEvents.Dispose();

Check warning on line 92 in src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader/ModuleLoader.cs

View check run for this annotation

Codecov / codecov/patch

src/module-loader/dotnet/src/MorganStanley.ComposeUI.ModuleLoader/ModuleLoader.cs#L91-L92

Added lines #L91 - L92 were not covered by tests
}

private async Task StopModuleInternal(IModuleInstance moduleInstance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal class NativeModuleRunner : IModuleRunner
{
public string ModuleType => ComposeUI.ModuleLoader.ModuleType.Native;

public Task Start(IModuleInstance moduleInstance, StartupContext startupContext, Func<Task> pipeline)
public Task Start(StartupContext startupContext, Func<Task> pipeline)
{
throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ internal class WebModuleRunner : IModuleRunner
{
public string ModuleType => ComposeUI.ModuleLoader.ModuleType.Web;

public async Task Start(IModuleInstance moduleInstance, StartupContext startupContext, Func<Task> pipeline)
public async Task Start(StartupContext startupContext, Func<Task> pipeline)
{
if (moduleInstance.Manifest.TryGetDetails(out WebManifestDetails details))
if (startupContext.ModuleInstance.Manifest.TryGetDetails(out WebManifestDetails details))
{
startupContext.AddProperty(new WebStartupProperties { IconUrl = details.IconUrl, Url = details.Url });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
// or implied. See the License for the specific language governing permissions
// and limitations under the License.

using Moq;

namespace MorganStanley.ComposeUI.ModuleLoader.Abstractions.Tests;

public class StartupContextTests
Expand All @@ -23,7 +25,7 @@ public void WhenAdd_AddedValuesCanBeRetrieved()
new MyContextInfo { Name = "Test2" }
};

StartupContext context = new StartupContext(new StartRequest("test"));
StartupContext context = new StartupContext(new StartRequest("test"), Mock.Of<IModuleInstance>());
context.AddProperty(expected[0]);
context.AddProperty(expected[1]);

Expand All @@ -35,7 +37,7 @@ public void WhenAdd_AddedValuesCanBeRetrieved()
[Fact]
public void GivenNullArgument_WhenAdd_ThrowsArgumentNullException()
{
StartupContext context = new StartupContext(new StartRequest("test"));
StartupContext context = new StartupContext(new StartRequest("test"), Mock.Of<IModuleInstance>());
Assert.Throws<ArgumentNullException>(() => context.AddProperty<object>(null!));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ public async Task WhenStart_WebStartupPropertiesAreAddedToStartupContext()
var moduleManifestMock = new MockModuleManifest(details);
moduleInstanceMock.Setup(m => m.Manifest).Returns(moduleManifestMock);
var startRequest = new StartRequest("test");
var startupContext = new StartupContext(startRequest);
var startupContext = new StartupContext(startRequest, moduleInstanceMock.Object);
static Task MockPipeline() => Task.CompletedTask;

var runner = new WebModuleRunner();
await runner.Start(moduleInstanceMock.Object, startupContext, MockPipeline);
await runner.Start(startupContext, MockPipeline);

var result = startupContext.GetProperties();
Assert.NotNull(result);
Expand Down
14 changes: 14 additions & 0 deletions src/shell/dotnet/Shell.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.Mes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppDirectory", "..\..\fdc3\dotnet\AppDirectory\src\AppDirectory\AppDirectory.csproj", "{70EAC402-B711-4528-B4DE-34081EB8676E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.ModuleLoader.Abstractions", "..\..\module-loader\dotnet\src\MorganStanley.ComposeUI.ModuleLoader.Abstractions\MorganStanley.ComposeUI.ModuleLoader.Abstractions.csproj", "{F94A14A9-38B2-4573-A74B-979044428152}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.ModuleLoader", "..\..\module-loader\dotnet\src\MorganStanley.ComposeUI.ModuleLoader\MorganStanley.ComposeUI.ModuleLoader.csproj", "{1D387A56-DC64-4EB0-870D-3872BE4716B4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -61,6 +65,14 @@ Global
{70EAC402-B711-4528-B4DE-34081EB8676E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70EAC402-B711-4528-B4DE-34081EB8676E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70EAC402-B711-4528-B4DE-34081EB8676E}.Release|Any CPU.Build.0 = Release|Any CPU
{F94A14A9-38B2-4573-A74B-979044428152}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F94A14A9-38B2-4573-A74B-979044428152}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F94A14A9-38B2-4573-A74B-979044428152}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F94A14A9-38B2-4573-A74B-979044428152}.Release|Any CPU.Build.0 = Release|Any CPU
{1D387A56-DC64-4EB0-870D-3872BE4716B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D387A56-DC64-4EB0-870D-3872BE4716B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D387A56-DC64-4EB0-870D-3872BE4716B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D387A56-DC64-4EB0-870D-3872BE4716B4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -73,6 +85,8 @@ Global
{7A94BC15-8FE5-4C84-8572-3C72248AFF8C} = {E7A2C581-4BF4-47A5-8A11-59B2DEBADCA7}
{3153E575-513F-4B1A-86D0-CBE5A7A8C606} = {E7A2C581-4BF4-47A5-8A11-59B2DEBADCA7}
{70EAC402-B711-4528-B4DE-34081EB8676E} = {E7A2C581-4BF4-47A5-8A11-59B2DEBADCA7}
{F94A14A9-38B2-4573-A74B-979044428152} = {E7A2C581-4BF4-47A5-8A11-59B2DEBADCA7}
{1D387A56-DC64-4EB0-870D-3872BE4716B4} = {E7A2C581-4BF4-47A5-8A11-59B2DEBADCA7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4C901E6C-4B9A-48C2-AB16-461040DC57B4}
Expand Down
12 changes: 12 additions & 0 deletions src/shell/dotnet/Shell.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Morgan Stanley makes this available to you under the Apache License,&#xD;
Version 2.0 (the "License"). You may obtain a copy of the License at&#xD;
&#xD;
http://www.apache.org/licenses/LICENSE-2.0.&#xD;
&#xD;
See the NOTICE file distributed with this work for additional information&#xD;
regarding copyright ownership. Unless required by applicable law or agreed&#xD;
to in writing, software distributed under the License is distributed on an&#xD;
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express&#xD;
or implied. See the License for the specific language governing permissions&#xD;
and limitations under the License.</s:String></wpf:ResourceDictionary>
20 changes: 20 additions & 0 deletions src/shell/dotnet/Shell/Abstractions/IInitializeAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Morgan Stanley makes this available to you under the Apache License,
// Version 2.0 (the "License"). You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0.
//
// See the NOTICE file distributed with this work for additional information
// regarding copyright ownership. Unless required by applicable law or agreed
// to in writing, software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions
// and limitations under the License.

using System.Threading.Tasks;

namespace MorganStanley.ComposeUI.Shell.Abstractions;

public interface IInitializeAsync
{
Task InitializeAsync();
}
Loading

0 comments on commit e12ce59

Please sign in to comment.