From c5388041e91033f04aafdfbe675a189340f340cf Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 24 May 2022 19:38:50 -0700 Subject: [PATCH 1/8] Used orleans for data syncing instead of observable collection --- ControlCenter/ControlCenter.csproj | 5 + ControlCenter/Data/AgentManager.cs | 157 ++++++++++++++++++++++++++++- ControlCenter/Hubs/AgentHub.cs | 11 +- ControlCenter/Pages/Index.razor | 16 ++- ControlCenter/Program.cs | 4 + 5 files changed, 178 insertions(+), 15 deletions(-) diff --git a/ControlCenter/ControlCenter.csproj b/ControlCenter/ControlCenter.csproj index f7d14ae..93744a9 100644 --- a/ControlCenter/ControlCenter.csproj +++ b/ControlCenter/ControlCenter.csproj @@ -8,6 +8,11 @@ + + + all + runtime; build; native; contentfiles; analyzers + diff --git a/ControlCenter/Data/AgentManager.cs b/ControlCenter/Data/AgentManager.cs index 494edd1..8e0ff24 100644 --- a/ControlCenter/Data/AgentManager.cs +++ b/ControlCenter/Data/AgentManager.cs @@ -1,7 +1,158 @@ using Contracts; -using System.Collections.ObjectModel; +using ControlCenter.Hubs; +using Microsoft.AspNetCore.SignalR; +using Orleans; +using Orleans.Concurrency; -public class AgentManager +public class AgentManager : IAgentEventSubscriber, IAsyncDisposable { - public ObservableCollection<(string, IAgent)> Agents { get; } = new(); + private readonly IAgentGrain _agentGrain; + private readonly IGrainFactory _grainFactory; + private Func _agentsChanged = () => { return Task.CompletedTask; }; + private IAgentEventSubscriber? _subscriberProxy; + private IHubContext _hubContext; + + public AgentManager(IGrainFactory grainFactory, IHubContext hubContext) + { + _grainFactory = grainFactory; + _hubContext = hubContext; + _agentGrain = grainFactory.GetGrain(0); + } + + public Task AddAgent(string connectionId) + { + return _agentGrain.AddAgent(connectionId); + } + + public Task RemoveAgent(string connectionId) + { + return _agentGrain.RemoveAgent(connectionId); + } + + public async Task<(string, IAgent)[]> GetAgents() + { + var agents = await _agentGrain.GetAgents(); + + return agents.Select(a => (a, AgentProxy.Create(_hubContext.Clients.Single(a)))).ToArray(); + } + + public Task OnAgentsChanged() + { + return _agentsChanged.Invoke(); + } + + public async Task SubscribeAsync(Func onChanged) + { + _agentsChanged += onChanged; + + if (_subscriberProxy is null) + { + _subscriberProxy = await _grainFactory.CreateObjectReference(this); + await _agentGrain.Subscribe(_subscriberProxy); + } + + return new AsyncDisposable(async () => + { + _agentsChanged -= onChanged; + }); + } + + public async ValueTask DisposeAsync() + { + if (_subscriberProxy is not null) + { + await _agentGrain.Unsubscribe(_subscriberProxy); + } + } + + private class AsyncDisposable : IAsyncDisposable + { + private readonly Func _callback; + public AsyncDisposable(Func callback) + { + _callback = callback; + } + public ValueTask DisposeAsync() + { + return _callback(); + } + } + + // Wrapper to workaround bug in signalr + private class AgentProxy : IAgent + { + private readonly ISingleClientProxy _clientProxy; + public AgentProxy(ISingleClientProxy clientProxy) + { + _clientProxy = clientProxy; + } + + public Task GetTemperature() + { + return _clientProxy.InvokeAsync(nameof(GetTemperature)); + } + + public Task Shutdown() + { + return _clientProxy.SendAsync(nameof(Shutdown)); + } + + public static IAgent Create(ISingleClientProxy clientProxy) => new AgentProxy(clientProxy); + } +} + +public interface IAgentEventSubscriber : IGrainObserver +{ + Task OnAgentsChanged(); +} + +interface IAgentGrain : IGrainWithIntegerKey +{ + Task AddAgent(string connectionId); + Task RemoveAgent(string connectionId); + Task Subscribe(IAgentEventSubscriber agentEventSubscriber); + Task Unsubscribe(IAgentEventSubscriber agentEventSubscriber); + + Task GetAgents(); +} + +[Reentrant] +public class AgentGrain : Grain, IAgentGrain +{ + private readonly List _agentIds = new(); + private readonly List _subs = new(); + + public async Task AddAgent(string connectionId) + { + _agentIds.Add(connectionId); + + foreach (var s in _subs) + { + await s.OnAgentsChanged(); + } + } + + public async Task RemoveAgent(string connectionId) + { + _agentIds.Remove(connectionId); + + foreach (var s in _subs) + { + await s.OnAgentsChanged(); + } + } + + public Task GetAgents() => Task.FromResult(_agentIds.ToArray()); + + public Task Subscribe(IAgentEventSubscriber agentEventSubscriber) + { + _subs.Add(agentEventSubscriber); + return Task.CompletedTask; + } + + public Task Unsubscribe(IAgentEventSubscriber agentEventSubscriber) + { + _subs.Remove(agentEventSubscriber); + return Task.CompletedTask; + } } diff --git a/ControlCenter/Hubs/AgentHub.cs b/ControlCenter/Hubs/AgentHub.cs index 6817720..bf5f6ad 100644 --- a/ControlCenter/Hubs/AgentHub.cs +++ b/ControlCenter/Hubs/AgentHub.cs @@ -15,21 +15,14 @@ public AgentHub(AgentManager agentManager) public override Task OnConnectedAsync() { - lock (_agentManager.Agents) - { - _agentManager.Agents.Add((Context.ConnectionId, Clients.Single(Context.ConnectionId))); - } + _agentManager.AddAgent(Context.ConnectionId); return base.OnConnectedAsync(); } public override Task OnDisconnectedAsync(Exception? exception) { - lock (_agentManager.Agents) - { - var item = _agentManager.Agents.FirstOrDefault(a => a.Item1 == Context.ConnectionId); - _agentManager.Agents.Remove(item); - } + _agentManager.RemoveAgent(Context.ConnectionId); return base.OnDisconnectedAsync(exception); } diff --git a/ControlCenter/Pages/Index.razor b/ControlCenter/Pages/Index.razor index 20509de..d05095f 100644 --- a/ControlCenter/Pages/Index.razor +++ b/ControlCenter/Pages/Index.razor @@ -15,7 +15,7 @@ - @foreach (var (id, agent) in Manager.Agents) + @foreach (var (id, agent) in agents) { @id @@ -44,10 +44,20 @@ @code { Dictionary _lastTemperature = new(); + (string, IAgent)[] agents = Array.Empty<(string, IAgent)>(); - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { - Manager.Agents.CollectionChanged += (_, _) => InvokeAsync(() => StateHasChanged()); + _ = await Manager.SubscribeAsync(async () => + { + agents = await Manager.GetAgents(); + + await InvokeAsync(() => StateHasChanged()); + }); + + agents = await Manager.GetAgents(); + + await base.OnInitializedAsync(); } async Task CheckTemperature(string id, IAgent agent) diff --git a/ControlCenter/Program.cs b/ControlCenter/Program.cs index 4889b17..f5ed69e 100644 --- a/ControlCenter/Program.cs +++ b/ControlCenter/Program.cs @@ -1,7 +1,11 @@ using ControlCenter.Hubs; +using Orleans; +using Orleans.Hosting; var builder = WebApplication.CreateBuilder(args); +builder.Host.UseOrleans(builder => builder.UseLocalhostClustering()); + // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); From f9f52195fb67fc4afeaa153b14bf38154186f30c Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 24 May 2022 21:28:00 -0700 Subject: [PATCH 2/8] Remove setting to work around issue in Hub --- ControlCenter/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ControlCenter/Program.cs b/ControlCenter/Program.cs index f5ed69e..7bc3d09 100644 --- a/ControlCenter/Program.cs +++ b/ControlCenter/Program.cs @@ -9,7 +9,6 @@ // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); -builder.Services.AddSignalR(o => o.MaximumParallelInvocationsPerClient = 2); builder.Services.AddSingleton(); var app = builder.Build(); From 5162223554d574dc1552f22009f9b7684dbd660d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 24 May 2022 23:46:00 -0700 Subject: [PATCH 3/8] Move signalr out of the agent manager --- ControlCenter/AgentProxy.cs | 26 ++++++++++++++++ ControlCenter/Data/AgentManager.cs | 50 +++++++----------------------- ControlCenter/Pages/Index.razor | 9 ++++-- 3 files changed, 44 insertions(+), 41 deletions(-) create mode 100644 ControlCenter/AgentProxy.cs diff --git a/ControlCenter/AgentProxy.cs b/ControlCenter/AgentProxy.cs new file mode 100644 index 0000000..19eb1a9 --- /dev/null +++ b/ControlCenter/AgentProxy.cs @@ -0,0 +1,26 @@ + + +// Wrapper to workaround bug in signalr +using Contracts; +using Microsoft.AspNetCore.SignalR; + +public class AgentProxy : IAgent +{ + private readonly ISingleClientProxy _clientProxy; + private AgentProxy(ISingleClientProxy clientProxy) + { + _clientProxy = clientProxy; + } + + public Task GetTemperature() + { + return _clientProxy.InvokeAsync(nameof(GetTemperature)); + } + + public Task Shutdown() + { + return _clientProxy.SendAsync(nameof(Shutdown)); + } + + public static IAgent Create(ISingleClientProxy clientProxy) => new AgentProxy(clientProxy); +} \ No newline at end of file diff --git a/ControlCenter/Data/AgentManager.cs b/ControlCenter/Data/AgentManager.cs index 8e0ff24..6213622 100644 --- a/ControlCenter/Data/AgentManager.cs +++ b/ControlCenter/Data/AgentManager.cs @@ -1,7 +1,4 @@ -using Contracts; -using ControlCenter.Hubs; -using Microsoft.AspNetCore.SignalR; -using Orleans; +using Orleans; using Orleans.Concurrency; public class AgentManager : IAgentEventSubscriber, IAsyncDisposable @@ -10,12 +7,10 @@ public class AgentManager : IAgentEventSubscriber, IAsyncDisposable private readonly IGrainFactory _grainFactory; private Func _agentsChanged = () => { return Task.CompletedTask; }; private IAgentEventSubscriber? _subscriberProxy; - private IHubContext _hubContext; - public AgentManager(IGrainFactory grainFactory, IHubContext hubContext) + public AgentManager(IGrainFactory grainFactory) { _grainFactory = grainFactory; - _hubContext = hubContext; _agentGrain = grainFactory.GetGrain(0); } @@ -29,11 +24,9 @@ public Task RemoveAgent(string connectionId) return _agentGrain.RemoveAgent(connectionId); } - public async Task<(string, IAgent)[]> GetAgents() + public async Task GetAgents() { - var agents = await _agentGrain.GetAgents(); - - return agents.Select(a => (a, AgentProxy.Create(_hubContext.Clients.Single(a)))).ToArray(); + return await _agentGrain.GetAgents(); } public Task OnAgentsChanged() @@ -41,7 +34,7 @@ public Task OnAgentsChanged() return _agentsChanged.Invoke(); } - public async Task SubscribeAsync(Func onChanged) + public async Task SubscribeAsync(Func onChanged) { _agentsChanged += onChanged; @@ -51,7 +44,7 @@ public async Task SubscribeAsync(Func onChanged) await _agentGrain.Subscribe(_subscriberProxy); } - return new AsyncDisposable(async () => + return new AsyncDisposable(() => { _agentsChanged -= onChanged; }); @@ -65,39 +58,18 @@ public async ValueTask DisposeAsync() } } - private class AsyncDisposable : IAsyncDisposable + private class AsyncDisposable : IDisposable { - private readonly Func _callback; - public AsyncDisposable(Func callback) + private readonly Action _callback; + public AsyncDisposable(Action callback) { _callback = callback; } - public ValueTask DisposeAsync() - { - return _callback(); - } - } - - // Wrapper to workaround bug in signalr - private class AgentProxy : IAgent - { - private readonly ISingleClientProxy _clientProxy; - public AgentProxy(ISingleClientProxy clientProxy) - { - _clientProxy = clientProxy; - } - - public Task GetTemperature() - { - return _clientProxy.InvokeAsync(nameof(GetTemperature)); - } - public Task Shutdown() + public void Dispose() { - return _clientProxy.SendAsync(nameof(Shutdown)); + _callback(); } - - public static IAgent Create(ISingleClientProxy clientProxy) => new AgentProxy(clientProxy); } } diff --git a/ControlCenter/Pages/Index.razor b/ControlCenter/Pages/Index.razor index d05095f..ddf5fee 100644 --- a/ControlCenter/Pages/Index.razor +++ b/ControlCenter/Pages/Index.razor @@ -1,6 +1,10 @@ @page "/" @using Contracts +@using ControlCenter.Hubs +@using Microsoft.AspNetCore.SignalR + @inject AgentManager Manager +@inject IHubContext HubContext Agents @@ -15,8 +19,9 @@ - @foreach (var (id, agent) in agents) + @foreach (var id in agents) { + var agent = AgentProxy.Create(HubContext.Clients.Single(id)); @id @@ -44,7 +49,7 @@ @code { Dictionary _lastTemperature = new(); - (string, IAgent)[] agents = Array.Empty<(string, IAgent)>(); + string[] agents = Array.Empty(); protected override async Task OnInitializedAsync() { From c8cfe8605b6b75bb7dd0091fd40cc71f8b202748 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 25 May 2022 00:29:02 -0700 Subject: [PATCH 4/8] Makes the collection logic generic --- ControlCenter/Data/AgentManager.cs | 124 +----------------- ControlCenter/Data/CollectionGrain.cs | 43 ++++++ .../Data/DistributedObservableCollection.cs | 64 +++++++++ ControlCenter/Data/ICollectionGrain.cs | 10 ++ ControlCenter/Data/ICollectionObserver.cs | 6 + .../Data/IDistirbutedCollectionFactory.cs | 21 +++ ControlCenter/Hubs/AgentHub.cs | 4 +- ControlCenter/Pages/Index.razor | 6 +- ControlCenter/Program.cs | 1 + 9 files changed, 155 insertions(+), 124 deletions(-) create mode 100644 ControlCenter/Data/CollectionGrain.cs create mode 100644 ControlCenter/Data/DistributedObservableCollection.cs create mode 100644 ControlCenter/Data/ICollectionGrain.cs create mode 100644 ControlCenter/Data/ICollectionObserver.cs create mode 100644 ControlCenter/Data/IDistirbutedCollectionFactory.cs diff --git a/ControlCenter/Data/AgentManager.cs b/ControlCenter/Data/AgentManager.cs index 6213622..5fe526c 100644 --- a/ControlCenter/Data/AgentManager.cs +++ b/ControlCenter/Data/AgentManager.cs @@ -1,130 +1,16 @@ using Orleans; -using Orleans.Concurrency; -public class AgentManager : IAgentEventSubscriber, IAsyncDisposable +public class AgentManager { - private readonly IAgentGrain _agentGrain; - private readonly IGrainFactory _grainFactory; - private Func _agentsChanged = () => { return Task.CompletedTask; }; - private IAgentEventSubscriber? _subscriberProxy; - - public AgentManager(IGrainFactory grainFactory) - { - _grainFactory = grainFactory; - _agentGrain = grainFactory.GetGrain(0); - } - - public Task AddAgent(string connectionId) - { - return _agentGrain.AddAgent(connectionId); - } - - public Task RemoveAgent(string connectionId) - { - return _agentGrain.RemoveAgent(connectionId); - } - - public async Task GetAgents() + public AgentManager(IDistirbutedCollectionFactory factory) { - return await _agentGrain.GetAgents(); + Collection = factory.Create(0); } - public Task OnAgentsChanged() - { - return _agentsChanged.Invoke(); - } - - public async Task SubscribeAsync(Func onChanged) - { - _agentsChanged += onChanged; - - if (_subscriberProxy is null) - { - _subscriberProxy = await _grainFactory.CreateObjectReference(this); - await _agentGrain.Subscribe(_subscriberProxy); - } - - return new AsyncDisposable(() => - { - _agentsChanged -= onChanged; - }); - } - - public async ValueTask DisposeAsync() - { - if (_subscriberProxy is not null) - { - await _agentGrain.Unsubscribe(_subscriberProxy); - } - } - - private class AsyncDisposable : IDisposable - { - private readonly Action _callback; - public AsyncDisposable(Action callback) - { - _callback = callback; - } - - public void Dispose() - { - _callback(); - } - } -} - -public interface IAgentEventSubscriber : IGrainObserver -{ - Task OnAgentsChanged(); + public DistributedObservableCollection Collection { get; } } -interface IAgentGrain : IGrainWithIntegerKey +class StringCollection : CollectionGrain { - Task AddAgent(string connectionId); - Task RemoveAgent(string connectionId); - Task Subscribe(IAgentEventSubscriber agentEventSubscriber); - Task Unsubscribe(IAgentEventSubscriber agentEventSubscriber); - - Task GetAgents(); -} - -[Reentrant] -public class AgentGrain : Grain, IAgentGrain -{ - private readonly List _agentIds = new(); - private readonly List _subs = new(); - - public async Task AddAgent(string connectionId) - { - _agentIds.Add(connectionId); - - foreach (var s in _subs) - { - await s.OnAgentsChanged(); - } - } - - public async Task RemoveAgent(string connectionId) - { - _agentIds.Remove(connectionId); - - foreach (var s in _subs) - { - await s.OnAgentsChanged(); - } - } - - public Task GetAgents() => Task.FromResult(_agentIds.ToArray()); - public Task Subscribe(IAgentEventSubscriber agentEventSubscriber) - { - _subs.Add(agentEventSubscriber); - return Task.CompletedTask; - } - - public Task Unsubscribe(IAgentEventSubscriber agentEventSubscriber) - { - _subs.Remove(agentEventSubscriber); - return Task.CompletedTask; - } } diff --git a/ControlCenter/Data/CollectionGrain.cs b/ControlCenter/Data/CollectionGrain.cs new file mode 100644 index 0000000..85efcd9 --- /dev/null +++ b/ControlCenter/Data/CollectionGrain.cs @@ -0,0 +1,43 @@ +using Orleans; +using Orleans.Concurrency; + +[Reentrant] +public class CollectionGrain : Grain, ICollectionGrain +{ + private readonly List _items = new(); + private readonly List _subs = new(); + + public async Task AddItem(T item) + { + _items.Add(item); + + foreach (var s in _subs) + { + await s.OnCollectionChanged(); + } + } + + public async Task RemoveItem(T item) + { + _items.Remove(item); + + foreach (var s in _subs) + { + await s.OnCollectionChanged(); + } + } + + public Task GetItems() => Task.FromResult(_items.ToArray()); + + public Task Subscribe(ICollectionObserver observer) + { + _subs.Add(observer); + return Task.CompletedTask; + } + + public Task Unsubscribe(ICollectionObserver observer) + { + _subs.Remove(observer); + return Task.CompletedTask; + } +} diff --git a/ControlCenter/Data/DistributedObservableCollection.cs b/ControlCenter/Data/DistributedObservableCollection.cs new file mode 100644 index 0000000..55803fa --- /dev/null +++ b/ControlCenter/Data/DistributedObservableCollection.cs @@ -0,0 +1,64 @@ + +using Orleans; + +public class DistributedObservableCollection : ICollectionObserver +{ + private readonly ICollectionGrain _collectionGrain; + private readonly IGrainFactory _grainFactory; + private ICollectionObserver? _collectionObserver; + private Func? _changed; + + public DistributedObservableCollection(IGrainFactory grainFactory, long key) + { + _grainFactory = grainFactory; + _collectionGrain = grainFactory.GetGrain>(key); + } + + public Task AddItem(T item) => _collectionGrain.AddItem(item); + public Task RemoveItem(T item) => _collectionGrain.RemoveItem(item); + public Task GetItems() => _collectionGrain.GetItems(); + + Task ICollectionObserver.OnCollectionChanged() + { + return _changed?.Invoke() ?? Task.CompletedTask; + } + + public async Task SubscribeAsync(Func onChanged) + { + _changed += onChanged; + + if (_collectionObserver is null) + { + _collectionObserver = await _grainFactory.CreateObjectReference(this); + await _collectionGrain.Subscribe(_collectionObserver); + } + + return new Disposable(() => + { + _changed -= onChanged; + }); + } + + public async ValueTask DisposeAsync() + { + if (_collectionObserver is not null) + { + await _collectionGrain.Unsubscribe(_collectionObserver); + } + } + + private class Disposable : IDisposable + { + private readonly Action _callback; + public Disposable(Action callback) + { + _callback = callback; + } + + public void Dispose() + { + _callback(); + } + } +} + diff --git a/ControlCenter/Data/ICollectionGrain.cs b/ControlCenter/Data/ICollectionGrain.cs new file mode 100644 index 0000000..672570c --- /dev/null +++ b/ControlCenter/Data/ICollectionGrain.cs @@ -0,0 +1,10 @@ +using Orleans; + +public interface ICollectionGrain : IGrainWithIntegerKey +{ + Task AddItem(T item); + Task RemoveItem(T item); + Task Subscribe(ICollectionObserver observer); + Task Unsubscribe(ICollectionObserver observer); + Task GetItems(); +} diff --git a/ControlCenter/Data/ICollectionObserver.cs b/ControlCenter/Data/ICollectionObserver.cs new file mode 100644 index 0000000..dc02c33 --- /dev/null +++ b/ControlCenter/Data/ICollectionObserver.cs @@ -0,0 +1,6 @@ +using Orleans; + +public interface ICollectionObserver : IGrainObserver +{ + Task OnCollectionChanged(); +} diff --git a/ControlCenter/Data/IDistirbutedCollectionFactory.cs b/ControlCenter/Data/IDistirbutedCollectionFactory.cs new file mode 100644 index 0000000..110c7e3 --- /dev/null +++ b/ControlCenter/Data/IDistirbutedCollectionFactory.cs @@ -0,0 +1,21 @@ + +using Orleans; + +public interface IDistirbutedCollectionFactory +{ + DistributedObservableCollection Create(long key); +} + +public class DistirbutedCollectionFactory : IDistirbutedCollectionFactory +{ + private readonly IGrainFactory _grainFactory; + public DistirbutedCollectionFactory(IGrainFactory grainFactory) + { + _grainFactory = grainFactory; + } + + public DistributedObservableCollection Create(long key) + { + return new DistributedObservableCollection(_grainFactory, key); + } +} \ No newline at end of file diff --git a/ControlCenter/Hubs/AgentHub.cs b/ControlCenter/Hubs/AgentHub.cs index bf5f6ad..4b08fd9 100644 --- a/ControlCenter/Hubs/AgentHub.cs +++ b/ControlCenter/Hubs/AgentHub.cs @@ -15,14 +15,14 @@ public AgentHub(AgentManager agentManager) public override Task OnConnectedAsync() { - _agentManager.AddAgent(Context.ConnectionId); + _agentManager.Collection.AddItem(Context.ConnectionId); return base.OnConnectedAsync(); } public override Task OnDisconnectedAsync(Exception? exception) { - _agentManager.RemoveAgent(Context.ConnectionId); + _agentManager.Collection.RemoveItem(Context.ConnectionId); return base.OnDisconnectedAsync(exception); } diff --git a/ControlCenter/Pages/Index.razor b/ControlCenter/Pages/Index.razor index ddf5fee..1af485c 100644 --- a/ControlCenter/Pages/Index.razor +++ b/ControlCenter/Pages/Index.razor @@ -53,14 +53,14 @@ protected override async Task OnInitializedAsync() { - _ = await Manager.SubscribeAsync(async () => + _ = await Manager.Collection.SubscribeAsync(async () => { - agents = await Manager.GetAgents(); + agents = await Manager.Collection.GetItems(); await InvokeAsync(() => StateHasChanged()); }); - agents = await Manager.GetAgents(); + agents = await Manager.Collection.GetItems(); await base.OnInitializedAsync(); } diff --git a/ControlCenter/Program.cs b/ControlCenter/Program.cs index 7bc3d09..7589a6a 100644 --- a/ControlCenter/Program.cs +++ b/ControlCenter/Program.cs @@ -9,6 +9,7 @@ // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); var app = builder.Build(); From 60e09a836fa6fec43da283977abfe7cb6801efe0 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 25 May 2022 00:36:10 -0700 Subject: [PATCH 5/8] Change the method name --- ControlCenter/Data/AgentManager.cs | 2 +- ControlCenter/Data/IDistirbutedCollectionFactory.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ControlCenter/Data/AgentManager.cs b/ControlCenter/Data/AgentManager.cs index 5fe526c..41240c4 100644 --- a/ControlCenter/Data/AgentManager.cs +++ b/ControlCenter/Data/AgentManager.cs @@ -4,7 +4,7 @@ public class AgentManager { public AgentManager(IDistirbutedCollectionFactory factory) { - Collection = factory.Create(0); + Collection = factory.CreateObservableCollection(0); } public DistributedObservableCollection Collection { get; } diff --git a/ControlCenter/Data/IDistirbutedCollectionFactory.cs b/ControlCenter/Data/IDistirbutedCollectionFactory.cs index 110c7e3..6dc654d 100644 --- a/ControlCenter/Data/IDistirbutedCollectionFactory.cs +++ b/ControlCenter/Data/IDistirbutedCollectionFactory.cs @@ -3,7 +3,7 @@ public interface IDistirbutedCollectionFactory { - DistributedObservableCollection Create(long key); + DistributedObservableCollection CreateObservableCollection(long key); } public class DistirbutedCollectionFactory : IDistirbutedCollectionFactory @@ -14,7 +14,7 @@ public DistirbutedCollectionFactory(IGrainFactory grainFactory) _grainFactory = grainFactory; } - public DistributedObservableCollection Create(long key) + public DistributedObservableCollection CreateObservableCollection(long key) { return new DistributedObservableCollection(_grainFactory, key); } From 8f62cd02187318fd4ad3b002f5c9d4193425fba3 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 25 May 2022 00:49:30 -0700 Subject: [PATCH 6/8] Clean up - Remove the ICollectionObserver interface from DistributedObservableCollection --- .../Data/DistributedObservableCollection.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ControlCenter/Data/DistributedObservableCollection.cs b/ControlCenter/Data/DistributedObservableCollection.cs index 55803fa..fe5ac71 100644 --- a/ControlCenter/Data/DistributedObservableCollection.cs +++ b/ControlCenter/Data/DistributedObservableCollection.cs @@ -1,24 +1,26 @@  using Orleans; -public class DistributedObservableCollection : ICollectionObserver +public class DistributedObservableCollection { private readonly ICollectionGrain _collectionGrain; private readonly IGrainFactory _grainFactory; - private ICollectionObserver? _collectionObserver; + private ICollectionObserver? _collectionObserverReference; private Func? _changed; + private readonly Observer _observer; public DistributedObservableCollection(IGrainFactory grainFactory, long key) { _grainFactory = grainFactory; _collectionGrain = grainFactory.GetGrain>(key); + _observer = new Observer(this); } public Task AddItem(T item) => _collectionGrain.AddItem(item); public Task RemoveItem(T item) => _collectionGrain.RemoveItem(item); public Task GetItems() => _collectionGrain.GetItems(); - Task ICollectionObserver.OnCollectionChanged() + private Task OnCollectionChanged() { return _changed?.Invoke() ?? Task.CompletedTask; } @@ -27,10 +29,10 @@ public async Task SubscribeAsync(Func onChanged) { _changed += onChanged; - if (_collectionObserver is null) + if (_collectionObserverReference is null) { - _collectionObserver = await _grainFactory.CreateObjectReference(this); - await _collectionGrain.Subscribe(_collectionObserver); + _collectionObserverReference = await _grainFactory.CreateObjectReference(_observer); + await _collectionGrain.Subscribe(_collectionObserverReference); } return new Disposable(() => @@ -41,12 +43,23 @@ public async Task SubscribeAsync(Func onChanged) public async ValueTask DisposeAsync() { - if (_collectionObserver is not null) + if (_collectionObserverReference is not null) { - await _collectionGrain.Unsubscribe(_collectionObserver); + await _collectionGrain.Unsubscribe(_collectionObserverReference); } } + private class Observer : ICollectionObserver + { + private readonly DistributedObservableCollection _parent; + public Observer(DistributedObservableCollection parent) + { + _parent = parent; + } + + public Task OnCollectionChanged() => _parent.OnCollectionChanged(); + } + private class Disposable : IDisposable { private readonly Action _callback; From b6815ca012db8e716bec0ec4671005d6b389b52f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 26 May 2022 11:12:47 -0700 Subject: [PATCH 7/8] Added namespaces to work around bug, --- ControlCenter/Data/AgentManager.cs | 9 ++------- ControlCenter/Data/CollectionGrain.cs | 2 ++ ControlCenter/Data/DistributedObservableCollection.cs | 1 + ControlCenter/Data/ICollectionGrain.cs | 2 ++ ...ectionFactory.cs => IDistributedCollectionFactory.cs} | 6 +++--- ControlCenter/Program.cs | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) rename ControlCenter/Data/{IDistirbutedCollectionFactory.cs => IDistributedCollectionFactory.cs} (69%) diff --git a/ControlCenter/Data/AgentManager.cs b/ControlCenter/Data/AgentManager.cs index 41240c4..3306f5b 100644 --- a/ControlCenter/Data/AgentManager.cs +++ b/ControlCenter/Data/AgentManager.cs @@ -2,15 +2,10 @@ public class AgentManager { - public AgentManager(IDistirbutedCollectionFactory factory) + public AgentManager(IDistributedCollectionFactory factory) { Collection = factory.CreateObservableCollection(0); } public DistributedObservableCollection Collection { get; } -} - -class StringCollection : CollectionGrain -{ - -} +} \ No newline at end of file diff --git a/ControlCenter/Data/CollectionGrain.cs b/ControlCenter/Data/CollectionGrain.cs index 85efcd9..87ddc96 100644 --- a/ControlCenter/Data/CollectionGrain.cs +++ b/ControlCenter/Data/CollectionGrain.cs @@ -1,6 +1,8 @@ using Orleans; using Orleans.Concurrency; +namespace Orleans.Collections; + [Reentrant] public class CollectionGrain : Grain, ICollectionGrain { diff --git a/ControlCenter/Data/DistributedObservableCollection.cs b/ControlCenter/Data/DistributedObservableCollection.cs index fe5ac71..532ff14 100644 --- a/ControlCenter/Data/DistributedObservableCollection.cs +++ b/ControlCenter/Data/DistributedObservableCollection.cs @@ -1,5 +1,6 @@  using Orleans; +using Orleans.Collections; public class DistributedObservableCollection { diff --git a/ControlCenter/Data/ICollectionGrain.cs b/ControlCenter/Data/ICollectionGrain.cs index 672570c..dd329fd 100644 --- a/ControlCenter/Data/ICollectionGrain.cs +++ b/ControlCenter/Data/ICollectionGrain.cs @@ -1,5 +1,7 @@ using Orleans; +namespace Orleans.Collections; + public interface ICollectionGrain : IGrainWithIntegerKey { Task AddItem(T item); diff --git a/ControlCenter/Data/IDistirbutedCollectionFactory.cs b/ControlCenter/Data/IDistributedCollectionFactory.cs similarity index 69% rename from ControlCenter/Data/IDistirbutedCollectionFactory.cs rename to ControlCenter/Data/IDistributedCollectionFactory.cs index 6dc654d..6d9ed67 100644 --- a/ControlCenter/Data/IDistirbutedCollectionFactory.cs +++ b/ControlCenter/Data/IDistributedCollectionFactory.cs @@ -1,15 +1,15 @@  using Orleans; -public interface IDistirbutedCollectionFactory +public interface IDistributedCollectionFactory { DistributedObservableCollection CreateObservableCollection(long key); } -public class DistirbutedCollectionFactory : IDistirbutedCollectionFactory +public class DistributedCollectionFactory : IDistributedCollectionFactory { private readonly IGrainFactory _grainFactory; - public DistirbutedCollectionFactory(IGrainFactory grainFactory) + public DistributedCollectionFactory(IGrainFactory grainFactory) { _grainFactory = grainFactory; } diff --git a/ControlCenter/Program.cs b/ControlCenter/Program.cs index 7589a6a..4a9fcfe 100644 --- a/ControlCenter/Program.cs +++ b/ControlCenter/Program.cs @@ -9,7 +9,7 @@ // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); var app = builder.Build(); From 211171cc866fdf96d5c61b5d449d481ce490aaa6 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 26 May 2022 23:09:53 -0700 Subject: [PATCH 8/8] Added everything to the namespace --- ControlCenter/Data/AgentManager.cs | 2 +- ControlCenter/Data/CollectionGrain.cs | 3 +-- .../Data/DistributedCollectionFactory.cs | 16 ++++++++++++++++ .../Data/DistributedObservableCollection.cs | 4 +--- ControlCenter/Data/ICollectionObserver.cs | 2 +- .../Data/IDistributedCollectionFactory.cs | 16 +--------------- ControlCenter/Program.cs | 1 + 7 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 ControlCenter/Data/DistributedCollectionFactory.cs diff --git a/ControlCenter/Data/AgentManager.cs b/ControlCenter/Data/AgentManager.cs index 3306f5b..92c9794 100644 --- a/ControlCenter/Data/AgentManager.cs +++ b/ControlCenter/Data/AgentManager.cs @@ -1,4 +1,4 @@ -using Orleans; +using Orleans.Collections; public class AgentManager { diff --git a/ControlCenter/Data/CollectionGrain.cs b/ControlCenter/Data/CollectionGrain.cs index 87ddc96..f1ffb65 100644 --- a/ControlCenter/Data/CollectionGrain.cs +++ b/ControlCenter/Data/CollectionGrain.cs @@ -1,5 +1,4 @@ -using Orleans; -using Orleans.Concurrency; +using Orleans.Concurrency; namespace Orleans.Collections; diff --git a/ControlCenter/Data/DistributedCollectionFactory.cs b/ControlCenter/Data/DistributedCollectionFactory.cs new file mode 100644 index 0000000..54ad1c0 --- /dev/null +++ b/ControlCenter/Data/DistributedCollectionFactory.cs @@ -0,0 +1,16 @@ + +namespace Orleans.Collections; + +public class DistributedCollectionFactory : IDistributedCollectionFactory +{ + private readonly IGrainFactory _grainFactory; + public DistributedCollectionFactory(IGrainFactory grainFactory) + { + _grainFactory = grainFactory; + } + + public DistributedObservableCollection CreateObservableCollection(long key) + { + return new DistributedObservableCollection(_grainFactory, key); + } +} \ No newline at end of file diff --git a/ControlCenter/Data/DistributedObservableCollection.cs b/ControlCenter/Data/DistributedObservableCollection.cs index 532ff14..a58a179 100644 --- a/ControlCenter/Data/DistributedObservableCollection.cs +++ b/ControlCenter/Data/DistributedObservableCollection.cs @@ -1,6 +1,4 @@ - -using Orleans; -using Orleans.Collections; +namespace Orleans.Collections; public class DistributedObservableCollection { diff --git a/ControlCenter/Data/ICollectionObserver.cs b/ControlCenter/Data/ICollectionObserver.cs index dc02c33..7b42a94 100644 --- a/ControlCenter/Data/ICollectionObserver.cs +++ b/ControlCenter/Data/ICollectionObserver.cs @@ -1,4 +1,4 @@ -using Orleans; +namespace Orleans.Collections; public interface ICollectionObserver : IGrainObserver { diff --git a/ControlCenter/Data/IDistributedCollectionFactory.cs b/ControlCenter/Data/IDistributedCollectionFactory.cs index 6d9ed67..c178656 100644 --- a/ControlCenter/Data/IDistributedCollectionFactory.cs +++ b/ControlCenter/Data/IDistributedCollectionFactory.cs @@ -1,21 +1,7 @@  -using Orleans; +namespace Orleans.Collections; public interface IDistributedCollectionFactory { DistributedObservableCollection CreateObservableCollection(long key); } - -public class DistributedCollectionFactory : IDistributedCollectionFactory -{ - private readonly IGrainFactory _grainFactory; - public DistributedCollectionFactory(IGrainFactory grainFactory) - { - _grainFactory = grainFactory; - } - - public DistributedObservableCollection CreateObservableCollection(long key) - { - return new DistributedObservableCollection(_grainFactory, key); - } -} \ No newline at end of file diff --git a/ControlCenter/Program.cs b/ControlCenter/Program.cs index 4a9fcfe..47da409 100644 --- a/ControlCenter/Program.cs +++ b/ControlCenter/Program.cs @@ -1,5 +1,6 @@ using ControlCenter.Hubs; using Orleans; +using Orleans.Collections; using Orleans.Hosting; var builder = WebApplication.CreateBuilder(args);