|
1 | 1 | import { PluginManager, getLazarPackageLatestVersion } from "ftc-panels" |
2 | | -import { config } from "../config" |
3 | | -import type { GraphEntry } from "./types" |
| 2 | +import { config } from "../config.js" |
4 | 3 |
|
5 | 4 | export default class Manager extends PluginManager { |
6 | | - GRAPH_PACKET_KEY = "graphPacket" |
7 | | - |
8 | | - private readonly perIdMax = 150 |
9 | | - private readonly overallMax: number | null = null |
10 | | - |
11 | | - override onInit(): void { |
12 | | - this.state.update(this.GRAPH_PACKET_KEY, []) |
13 | | - |
14 | | - this.socket.addMessageHandler( |
15 | | - this.GRAPH_PACKET_KEY, |
16 | | - (data: GraphEntry[]) => { |
17 | | - this.state.mutate(this.GRAPH_PACKET_KEY, (current: GraphEntry[]) => { |
18 | | - const merged = current.concat(data) |
19 | | - |
20 | | - const now = Date.now() |
21 | | - const cutoff = now - 60_000 |
22 | | - const recent = merged.filter((e) => e.timestamp >= cutoff) |
23 | | - |
24 | | - recent.sort((a, b) => a.timestamp - b.timestamp) |
25 | | - |
26 | | - const perIdSampled = downsamplePerId(recent, this.perIdMax) |
27 | | - |
28 | | - const finalEntries = this.overallMax |
29 | | - ? proportionalGlobalCap(perIdSampled, this.overallMax) |
30 | | - : perIdSampled |
31 | | - |
32 | | - finalEntries.sort((a, b) => a.timestamp - b.timestamp) |
33 | | - return finalEntries |
34 | | - }) |
35 | | - } |
36 | | - ) |
37 | | - } |
| 5 | + override onInit(): void {} |
38 | 6 |
|
39 | 7 | static async getNewVersion(): Promise<string> { |
40 | 8 | return await getLazarPackageLatestVersion(config.id) |
41 | 9 | } |
42 | 10 | } |
43 | | - |
44 | | -function downsamplePerId( |
45 | | - entries: GraphEntry[], |
46 | | - perIdMax: number |
47 | | -): GraphEntry[] { |
48 | | - const byId = new Map<string, GraphEntry[]>() |
49 | | - for (const e of entries) { |
50 | | - const arr = byId.get(e.id) |
51 | | - if (arr) arr.push(e) |
52 | | - else byId.set(e.id, [e]) |
53 | | - } |
54 | | - |
55 | | - const out: GraphEntry[] = [] |
56 | | - for (const [id, group] of byId) { |
57 | | - group.sort((a, b) => a.timestamp - b.timestamp) |
58 | | - if (group.length <= perIdMax) { |
59 | | - out.push(...group) |
60 | | - continue |
61 | | - } |
62 | | - out.push(...uniformPickNearest(group, perIdMax)) |
63 | | - } |
64 | | - return out |
65 | | -} |
66 | | - |
67 | | -function uniformPickNearest( |
68 | | - group: GraphEntry[], |
69 | | - maxCount: number |
70 | | -): GraphEntry[] { |
71 | | - const n = group.length |
72 | | - if (n <= maxCount) return group.slice() |
73 | | - |
74 | | - const t0 = group[0].timestamp |
75 | | - const t1 = group[n - 1].timestamp |
76 | | - if (t1 <= t0) return group.slice(-maxCount) |
77 | | - |
78 | | - const step = (t1 - t0) / (maxCount - 1) |
79 | | - const result: GraphEntry[] = [] |
80 | | - |
81 | | - let j = 0 |
82 | | - const used = new Set<number>() |
83 | | - for (let i = 0; i < maxCount; i++) { |
84 | | - const targetT = Math.round(t0 + i * step) |
85 | | - |
86 | | - while (j < n - 1 && group[j].timestamp < targetT) j++ |
87 | | - |
88 | | - const cand: number[] = [] |
89 | | - if (j > 0) cand.push(j - 1) |
90 | | - cand.push(j) |
91 | | - |
92 | | - let bestIdx = cand[0] |
93 | | - let bestDist = Math.abs(group[bestIdx].timestamp - targetT) |
94 | | - for (let k = 1; k < cand.length; k++) { |
95 | | - const idx = cand[k] |
96 | | - const dist = Math.abs(group[idx].timestamp - targetT) |
97 | | - if ( |
98 | | - dist < bestDist || |
99 | | - (dist === bestDist && group[idx].timestamp < group[bestIdx].timestamp) |
100 | | - ) { |
101 | | - bestIdx = idx |
102 | | - bestDist = dist |
103 | | - } |
104 | | - } |
105 | | - if (used.has(bestIdx)) { |
106 | | - let left = bestIdx - 1 |
107 | | - let right = bestIdx + 1 |
108 | | - while (left >= 0 || right < n) { |
109 | | - let pick = -1 |
110 | | - const leftDist = |
111 | | - left >= 0 ? Math.abs(group[left].timestamp - targetT) : Infinity |
112 | | - const rightDist = |
113 | | - right < n ? Math.abs(group[right].timestamp - targetT) : Infinity |
114 | | - if (leftDist <= rightDist && left >= 0 && !used.has(left)) pick = left |
115 | | - else if (right < n && !used.has(right)) pick = right |
116 | | - |
117 | | - if (pick !== -1) { |
118 | | - bestIdx = pick |
119 | | - break |
120 | | - } |
121 | | - if (left >= 0) left-- |
122 | | - if (right < n) right++ |
123 | | - } |
124 | | - } |
125 | | - |
126 | | - used.add(bestIdx) |
127 | | - result.push(group[bestIdx]) |
128 | | - } |
129 | | - |
130 | | - result.sort((a, b) => a.timestamp - b.timestamp) |
131 | | - return result |
132 | | -} |
133 | | - |
134 | | -function proportionalGlobalCap( |
135 | | - entries: GraphEntry[], |
136 | | - overallMax: number |
137 | | -): GraphEntry[] { |
138 | | - if (entries.length <= overallMax) return entries |
139 | | - |
140 | | - const byId = new Map<string, GraphEntry[]>() |
141 | | - for (const e of entries) { |
142 | | - const arr = byId.get(e.id) |
143 | | - if (arr) arr.push(e) |
144 | | - else byId.set(e.id, [e]) |
145 | | - } |
146 | | - |
147 | | - const ids = Array.from(byId.keys()) |
148 | | - const counts = ids.map((id) => byId.get(id)!.length) |
149 | | - const total = counts.reduce((a, b) => a + b, 0) |
150 | | - |
151 | | - let quotas = counts.map((c) => |
152 | | - Math.max(1, Math.floor((c / total) * overallMax)) |
153 | | - ) |
154 | | - |
155 | | - let diff = overallMax - quotas.reduce((a, b) => a + b, 0) |
156 | | - const fracs = counts.map((c, i) => (c / total) * overallMax - quotas[i]) |
157 | | - const order = fracs |
158 | | - .map((f, i) => ({ i, f })) |
159 | | - .sort((a, b) => b.f - a.f) |
160 | | - .map((o) => o.i) |
161 | | - |
162 | | - let k = 0 |
163 | | - while (diff !== 0) { |
164 | | - const idx = order[k % order.length] |
165 | | - const newQuota = quotas[idx] + Math.sign(diff) |
166 | | - quotas[idx] = Math.max(1, Math.min(newQuota, counts[idx])) |
167 | | - diff -= Math.sign(diff) |
168 | | - k++ |
169 | | - } |
170 | | - |
171 | | - const out: GraphEntry[] = [] |
172 | | - for (let m = 0; m < ids.length; m++) { |
173 | | - const id = ids[m] |
174 | | - const group = byId |
175 | | - .get(id)! |
176 | | - .slice() |
177 | | - .sort((a, b) => a.timestamp - b.timestamp) |
178 | | - const q = quotas[m] |
179 | | - out.push(...uniformPickNearest(group, q)) |
180 | | - } |
181 | | - |
182 | | - out.sort((a, b) => a.timestamp - b.timestamp) |
183 | | - return out |
184 | | -} |
0 commit comments