Forge
csharpdeeb25a2
1using CascadeIDE.Models;
2
3namespace CascadeIDE.Cockpit.Composition.HostSurface;
4
5/// <summary>
6/// Runtime-карта размещения инструментов (bundle/repo + user) для пары <c>surface_id + slot_id</c>.
7/// </summary>
8public static class InstrumentPlacementRuntime
9{
10 private static readonly Lock Gate = new();
11 private static Dictionary<string, string> _workspaceMap = BuildCodeDefaults();
12
13 private static Dictionary<string, string> BuildCodeDefaults() =>
14 new(StringComparer.OrdinalIgnoreCase)
15 {
16 [BuildKey(MainWindowHostSurfaceIds.DockedGrid, CockpitSlotIds.Pfd)] = CockpitStandardInstrumentIds.SolutionExplorerTree,
17 [BuildKey(MainWindowHostSurfaceIds.PlusMfdHostTopLevel, CockpitSlotIds.Pfd)] = CockpitStandardInstrumentIds.SolutionExplorerTree,
18 [BuildKey(MainWindowHostSurfaceIds.PlusPfdHostTopLevel, CockpitSlotIds.Pfd)] = CockpitStandardInstrumentIds.SolutionExplorerTree,
19 [BuildKey(MainWindowHostSurfaceIds.PlusPfdMfdHostTopLevel, CockpitSlotIds.Pfd)] = CockpitStandardInstrumentIds.SolutionExplorerTree,
20 };
21
22 internal static void ResetToCodeDefaults()
23 {
24 lock (Gate)
25 _workspaceMap = BuildCodeDefaults();
26 }
27
28 internal static void ApplyWorkspaceInstrumentRouting(IReadOnlyDictionary<string, string>? routing)
29 {
30 lock (Gate)
31 {
32 var map = BuildCodeDefaults();
33 ApplyRoutingOverlay(map, routing, "workspace");
34 _workspaceMap = map;
35 }
36 }
37
38 /// <param name="display">Если <c>null</c>, используется только workspace-карта (как при отсутствии user-слоя).</param>
39 public static bool TryResolveInstrument(
40 string surfaceId,
41 string slotId,
42 DisplaySettings? display,
43 out string instrumentId)
44 {
45 instrumentId = "";
46
47 if (display is null)
48 return TryResolveWorkspaceOnly(surfaceId, slotId, out instrumentId);
49
50 var userMap = BuildUserPlacementMap(display);
51 var key = BuildKey(surfaceId, slotId);
52 var preferRepo = display.PreferRepoInstruments;
53
54 lock (Gate)
55 {
56 if (preferRepo)
57 {
58 if (_workspaceMap.TryGetValue(key, out var repoValue))
59 {
60 instrumentId = repoValue;
61 return true;
62 }
63
64 if (userMap.TryGetValue(key, out var userValue))
65 {
66 instrumentId = userValue;
67 return true;
68 }
69
70 return false;
71 }
72
73 if (userMap.TryGetValue(key, out var userFirstValue))
74 {
75 instrumentId = userFirstValue;
76 return true;
77 }
78
79 if (_workspaceMap.TryGetValue(key, out var repoSecondValue))
80 {
81 instrumentId = repoSecondValue;
82 return true;
83 }
84
85 return false;
86 }
87 }
88
89 private static bool TryResolveWorkspaceOnly(string surfaceId, string slotId, out string instrumentId)
90 {
91 var key = BuildKey(surfaceId, slotId);
92 lock (Gate)
93 {
94 if (_workspaceMap.TryGetValue(key, out var value))
95 {
96 instrumentId = value;
97 return true;
98 }
99 }
100
101 instrumentId = "";
102 return false;
103 }
104
105 private static Dictionary<string, string> BuildUserPlacementMap(DisplaySettings display)
106 {
107 var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
108 ApplyRoutingOverlay(map, display.Instruments, "user");
109 return map;
110 }
111
112 /// <summary>
113 /// Расширяет <c>pfd_primary</c>/<c>mfd_primary</c> в конкретные ключи поверхностей рантайма (без <c>surface_id</c> в TOML).
114 /// </summary>
115 internal static void ApplyRoutingOverlay(
116 Dictionary<string, string> map,
117 IReadOnlyDictionary<string, string>? routing,
118 string source)
119 {
120 if (routing is null || routing.Count == 0)
121 return;
122
123 foreach (var kv in routing)
124 {
125 var routeKey = kv.Key.Trim();
126 var raw = kv.Value?.Trim() ?? "";
127 if (routeKey.Length == 0 || raw.Length == 0)
128 continue;
129
130 if (!InstrumentRoutingAliasResolver.TryResolve(raw, out var canonical))
131 {
132 global::System.Diagnostics.Debug.WriteLine(
133 $"InstrumentPlacementRuntime: unknown instrument alias or id '{raw}' for '{routeKey}' ({source})");
134 continue;
135 }
136
137 if (routeKey.Equals(InstrumentRoutingSlotKeys.PfdPrimary, StringComparison.OrdinalIgnoreCase))
138 {
139 map[BuildKey(MainWindowHostSurfaceIds.DockedGrid, CockpitSlotIds.Pfd)] = canonical;
140 map[BuildKey(MainWindowHostSurfaceIds.PlusMfdHostTopLevel, CockpitSlotIds.Pfd)] = canonical;
141 map[BuildKey(MainWindowHostSurfaceIds.PlusPfdHostTopLevel, CockpitSlotIds.Pfd)] = canonical;
142 map[BuildKey(MainWindowHostSurfaceIds.PlusPfdMfdHostTopLevel, CockpitSlotIds.Pfd)] = canonical;
143 }
144 else if (routeKey.Equals(InstrumentRoutingSlotKeys.MfdPrimary, StringComparison.OrdinalIgnoreCase))
145 {
146 map[BuildKey(MainWindowHostSurfaceIds.DockedGrid, CockpitSlotIds.Mfd)] = canonical;
147 map[BuildKey(MainWindowHostSurfaceIds.PlusMfdHostTopLevel, CockpitSlotIds.Mfd)] = canonical;
148 map[BuildKey(MainWindowHostSurfaceIds.PlusPfdHostTopLevel, CockpitSlotIds.Mfd)] = canonical;
149 map[BuildKey(MainWindowHostSurfaceIds.PlusPfdMfdHostTopLevel, CockpitSlotIds.Mfd)] = canonical;
150 }
151 else
152 {
153 global::System.Diagnostics.Debug.WriteLine(
154 $"InstrumentPlacementRuntime: unknown routing key '{routeKey}' ({source})");
155 }
156 }
157 }
158
159 private static string BuildKey(string surfaceId, string slotId) =>
160 $"{surfaceId.Trim().ToLowerInvariant()}::{slotId.Trim().ToLowerInvariant()}";
161}
162
View only · write via MCP/CIDE