Forge
csharp4405de34
1using System.Threading.Channels;
2
3namespace CascadeIDE.Cockpit.DataBus;
4
5/// <summary>In-process implementation of <see cref="IDataBus"/>.</summary>
6public sealed class InMemoryDataBus : IDataBus, IDisposable
7{
8 private readonly Lock _sync = new();
9 private readonly Dictionary<Type, List<Delegate>> _handlers = [];
10 private readonly Dictionary<Type, object> _routes = [];
11 private readonly bool _asynchronousDispatch;
12 private readonly CancellationTokenSource? _dispatchCts;
13 private readonly DataBusEventPolicy _eventPolicy;
14
15 public InMemoryDataBus(bool asynchronousDispatch = false, DataBusEventPolicy? eventPolicy = null)
16 {
17 _asynchronousDispatch = asynchronousDispatch;
18 _dispatchCts = asynchronousDispatch ? new CancellationTokenSource() : null;
19 _eventPolicy = eventPolicy ?? DataBusEventPolicyLoader.Load();
20 }
21
22 public void Publish<TEvent>(TEvent evt)
23 {
24 if (!_asynchronousDispatch)
25 {
26 DispatchToSubscribers(evt);
27 return;
28 }
29
30 var route = GetOrCreateRoute<TEvent>();
31 route.Publish(evt);
32 }
33
34 public IDisposable Subscribe<TEvent>(Action<TEvent> handler)
35 {
36 ArgumentNullException.ThrowIfNull(handler);
37
38 lock (_sync)
39 {
40 if (!_handlers.TryGetValue(typeof(TEvent), out var list))
41 {
42 list = [];
43 _handlers[typeof(TEvent)] = list;
44 }
45 list.Add(handler);
46 }
47
48 return new Subscription(() => Unsubscribe(handler));
49 }
50
51 public void Dispose()
52 {
53 _dispatchCts?.Cancel();
54 _dispatchCts?.Dispose();
55 }
56
57 private void DispatchToSubscribers<TEvent>(TEvent evt)
58 {
59 Delegate[] snapshot;
60 lock (_sync)
61 {
62 if (!_handlers.TryGetValue(typeof(TEvent), out var list) || list.Count == 0)
63 return;
64 snapshot = [.. list];
65 }
66
67 foreach (var del in snapshot)
68 {
69 if (del is Action<TEvent> handler)
70 {
71 try
72 {
73 handler(evt);
74 }
75 catch
76 {
77 // Isolate subscribers: one faulty handler must not break delivery to others.
78 }
79 }
80 }
81 }
82
83 private EventRoute<TEvent> GetOrCreateRoute<TEvent>()
84 {
85 lock (_sync)
86 {
87 if (_routes.TryGetValue(typeof(TEvent), out var existing) && existing is EventRoute<TEvent> route)
88 return route;
89
90 var created = EventRoute<TEvent>.Create(this, _eventPolicy.IsBurst(typeof(TEvent)), _dispatchCts?.Token ?? CancellationToken.None);
91 _routes[typeof(TEvent)] = created;
92 return created;
93 }
94 }
95
96 private sealed class EventRoute<TEvent>
97 {
98 private readonly InMemoryDataBus _owner;
99 private readonly Channel<TEvent> _channel;
100 private readonly CancellationToken _cancellationToken;
101
102 private EventRoute(InMemoryDataBus owner, Channel<TEvent> channel, CancellationToken cancellationToken)
103 {
104 _owner = owner;
105 _channel = channel;
106 _cancellationToken = cancellationToken;
107 _ = Task.Run(DispatchLoopAsync, CancellationToken.None);
108 }
109
110 public static EventRoute<TEvent> Create(InMemoryDataBus owner, bool latestWinsBurst, CancellationToken cancellationToken)
111 {
112 if (latestWinsBurst)
113 {
114 var bounded = Channel.CreateBounded<TEvent>(new BoundedChannelOptions(1)
115 {
116 SingleReader = true,
117 SingleWriter = false,
118 FullMode = BoundedChannelFullMode.DropOldest
119 });
120 return new EventRoute<TEvent>(owner, bounded, cancellationToken);
121 }
122
123 var unbounded = Channel.CreateUnbounded<TEvent>(new UnboundedChannelOptions
124 {
125 SingleReader = true,
126 SingleWriter = false
127 });
128 return new EventRoute<TEvent>(owner, unbounded, cancellationToken);
129 }
130
131 public void Publish(TEvent evt)
132 {
133 _channel.Writer.TryWrite(evt);
134 }
135
136 private async Task DispatchLoopAsync()
137 {
138 try
139 {
140 await foreach (var evt in _channel.Reader.ReadAllAsync(_cancellationToken))
141 _owner.DispatchToSubscribers(evt);
142 }
143 catch (OperationCanceledException)
144 {
145 // Expected on dispose.
146 }
147 }
148 }
149
150 private void Unsubscribe<TEvent>(Action<TEvent> handler)
151 {
152 lock (_sync)
153 {
154 if (!_handlers.TryGetValue(typeof(TEvent), out var list))
155 return;
156 list.Remove(handler);
157 if (list.Count == 0)
158 _handlers.Remove(typeof(TEvent));
159 }
160 }
161
162 private sealed class Subscription(Action dispose) : IDisposable
163 {
164 private Action? _dispose = dispose;
165
166 public void Dispose()
167 {
168 var d = Interlocked.Exchange(ref _dispose, null);
169 d?.Invoke();
170 }
171 }
172}
173
View only · write via MCP/CIDE