Skip to content

Commit 77eda14

Browse files
authored
Merge branch 'main' into prod-vscode-uri
2 parents 9ecdb38 + 97207b4 commit 77eda14

24 files changed

Lines changed: 413 additions & 449 deletions

src/views/component-viewer/component-viewer-instance.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
import * as vscode from 'vscode';
1818
import { URI } from 'vscode-uri';
19+
import { createHash } from 'crypto';
1920
import { parseStringPromise, ParserOptions } from 'xml2js';
2021
import { Json } from './model/scvd-base';
2122
import { Resolver } from './resolver';
2223
import { ScvdComponentViewer } from './model/scvd-component-viewer';
23-
import { ScvdBase } from './model/scvd-base';
2424
import { StatementEngine } from './statement-engine/statement-engine';
2525
import { ScvdEvalContext } from './scvd-eval-context';
2626
import { GDBTargetDebugSession, GDBTargetDebugTracker } from '../../debug-session';
@@ -38,12 +38,18 @@ const xmlOpts: ParserOptions = {
3838
};
3939

4040
export class ComponentViewerInstance {
41+
// Cache final file key per path to keep IDs stable across updates.
42+
private static _fileKeysByPath: Map<string, string> = new Map<string, string>();
43+
// Track how many times a base hash was used to disambiguate collisions.
44+
private static _fileKeyCounts: Map<string, number> = new Map<string, number>();
45+
4146
private _model: ScvdComponentViewer | undefined;
4247
private _memUsageStart: number = 0;
4348
private _memUsageLast: number = 0;
4449
private _timeUsageLast: number = 0;
4550
private _statementEngine: StatementEngine | undefined;
4651
private _guiTree: ScvdGuiTree | undefined;
52+
private _fileKey: string | undefined;
4753
private _scvdEvalContext: ScvdEvalContext | undefined;
4854

4955
public constructor(
@@ -103,6 +109,7 @@ export class ComponentViewerInstance {
103109

104110
public async readModel(filename: URI, debugSession: GDBTargetDebugSession, debugTracker: GDBTargetDebugTracker): Promise<void> {
105111
const stats: string[] = [];
112+
this._fileKey = ComponentViewerInstance.getFileKey(filename);
106113

107114
stats.push(this.getStats(` Start reading SCVD file ${filename}`));
108115
const buf = (await this.readFileToBuffer(filename)).toString('utf-8');
@@ -117,7 +124,6 @@ export class ComponentViewerInstance {
117124
return;
118125
}
119126

120-
ScvdBase.resetIds();
121127
this.model = new ScvdComponentViewer(undefined);
122128
if (!this.model) {
123129
console.error('Failed to create SCVD model');
@@ -150,7 +156,9 @@ export class ComponentViewerInstance {
150156
this.statementEngine = new StatementEngine(this.model, executionContext);
151157
this.statementEngine.initialize();
152158
stats.push(this.getStats(' statementEngine.initialize'));
153-
this._guiTree = new ScvdGuiTree(undefined, 'component-viewer-root');
159+
this._guiTree = new ScvdGuiTree(undefined);
160+
this._guiTree.setId(this._fileKey);
161+
this._guiTree.setGuiName('component-viewer-root');
154162

155163
console.log('ComponentViewerInstance readModel stats:\n' + stats.join('\n '));
156164
}
@@ -161,9 +169,8 @@ export class ComponentViewerInstance {
161169
if (this._statementEngine === undefined || this._guiTree === undefined) {
162170
return;
163171
}
164-
const epoch = this._guiTree.beginUpdate();
172+
this._guiTree.clear();
165173
await this._statementEngine.executeAll(this._guiTree);
166-
this._guiTree.finalizeUpdate(epoch);
167174
stats.push(this.getStats('end'));
168175
console.log('ComponentViewerInstance update stats:\n' + stats.join('\n '));
169176
}
@@ -188,6 +195,21 @@ export class ComponentViewerInstance {
188195
}
189196
}
190197

198+
private static getFileKey(filename: URI): string {
199+
const filePath = filename.fsPath ?? filename.toString();
200+
const existing = ComponentViewerInstance._fileKeysByPath.get(filePath);
201+
if (existing !== undefined) {
202+
return existing;
203+
}
204+
const baseHash = createHash('sha1').update(filePath).digest('hex').slice(0, 16);
205+
const nextCount = ComponentViewerInstance._fileKeyCounts.get(baseHash) ?? 0;
206+
const suffix = nextCount === 0 ? '' : `-f${nextCount}`;
207+
const key = `${baseHash}${suffix}`;
208+
ComponentViewerInstance._fileKeyCounts.set(baseHash, nextCount + 1);
209+
ComponentViewerInstance._fileKeysByPath.set(filePath, key);
210+
return key;
211+
}
212+
191213
public get model(): ScvdComponentViewer | undefined {
192214
return this._model;
193215
}

src/views/component-viewer/component-viewer-main.ts

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,37 @@ import { GDBTargetDebugTracker, GDBTargetDebugSession } from '../../debug-sessio
1919
import { ComponentViewerInstance } from './component-viewer-instance';
2020
import { URI } from 'vscode-uri';
2121
import { ComponentViewerTreeDataProvider } from './component-viewer-tree-view';
22+
import { logger } from '../../logger';
23+
import type { ScvdGuiInterface } from './model/scvd-gui-interface';
2224

23-
25+
export type fifoUpdateReason = 'sessionChanged' | 'refreshTimer' | 'stackTrace';
26+
interface UpdateQueueItem {
27+
updateId: number;
28+
debugSession: GDBTargetDebugSession;
29+
updateReason: fifoUpdateReason;
30+
}
2431
export class ComponentViewer {
2532
private _activeSession: GDBTargetDebugSession | undefined;
2633
private _instances: ComponentViewerInstance[] = [];
2734
private _componentViewerTreeDataProvider: ComponentViewerTreeDataProvider | undefined;
2835
private _context: vscode.ExtensionContext;
2936
private _instanceUpdateCounter: number = 0;
3037
private _loadingCounter: number = 0;
38+
// Update queue is currently used for logging purposes only
39+
private _updateQueue: UpdateQueueItem[] = [];
40+
private _pendingUpdateTimer: NodeJS.Timeout | undefined;
41+
private _pendingUpdate: boolean = false;
42+
private _runningUpdate: boolean = false;
43+
private static readonly pendingUpdateDelayMs = 200;
3144

3245
public constructor(context: vscode.ExtensionContext) {
3346
this._context = context;
3447
}
3548

3649
public activate(tracker: GDBTargetDebugTracker): void {
37-
/* Create Tree Viewer */
3850
this._componentViewerTreeDataProvider = new ComponentViewerTreeDataProvider();
3951
const treeProviderDisposable = vscode.window.registerTreeDataProvider('cmsis-debugger.componentViewer', this._componentViewerTreeDataProvider);
40-
this._context.subscriptions.push(
41-
treeProviderDisposable);
52+
this._context.subscriptions.push(treeProviderDisposable);
4253
// Subscribe to debug tracker events to update active session
4354
this.subscribetoDebugTrackerEvents(this._context, tracker);
4455
}
@@ -111,14 +122,16 @@ export class ComponentViewer {
111122
this._activeSession = undefined;
112123
}
113124
// Update component viewer instance(s)
114-
await this.updateInstances();
125+
this.schedulePendingUpdate('stackTrace');
115126
}
116127

117128
private async handleOnWillStopSession(session: GDBTargetDebugSession): Promise<void> {
118129
// Clear active session if it is the one being stopped
119130
if (this._activeSession?.session.id === session.session.id) {
120131
this._activeSession = undefined;
121132
}
133+
// Clearing update queue
134+
this._updateQueue = [];
122135
}
123136

124137
private async handleOnWillStartSession(session: GDBTargetDebugSession): Promise<void> {
@@ -130,7 +143,7 @@ export class ComponentViewer {
130143
// if new session is not the current active session, erase old instances and read the new ones
131144
if (this._activeSession?.session.id !== session.session.id) {
132145
this._instances = [];
133-
this._componentViewerTreeDataProvider?.deleteModels();
146+
this._componentViewerTreeDataProvider?.clear();
134147
}
135148
// Update debug session
136149
this._activeSession = session;
@@ -141,42 +154,76 @@ export class ComponentViewer {
141154
private async handleRefreshTimerEvent(session: GDBTargetDebugSession): Promise<void> {
142155
if (this._activeSession?.session.id === session.session.id) {
143156
// Update component viewer instance(s)
144-
await this.updateInstances();
157+
this.schedulePendingUpdate('refreshTimer');
145158
}
146159
}
147160

148161
private async handleOnDidChangeActiveDebugSession(session: GDBTargetDebugSession | undefined): Promise<void> {
149162
// Update debug session
150163
this._activeSession = session;
151-
if (!session) {
152-
this._instances = [];
153-
this._componentViewerTreeDataProvider?.deleteModels();
164+
if (session === undefined) {
154165
return;
155166
}
156-
// Update Active Session in all instances
157-
for (const instance of this._instances) {
158-
instance.updateActiveSession(session);
167+
// update active debug session for all instances
168+
this._instances.forEach((instance) => instance.updateActiveSession(session));
169+
this.schedulePendingUpdate('sessionChanged');
170+
}
171+
172+
private schedulePendingUpdate(updateReason: fifoUpdateReason): void {
173+
this._pendingUpdate = true;
174+
if (this._pendingUpdateTimer) {
175+
clearTimeout(this._pendingUpdateTimer);
159176
}
160-
// Update component viewer instance(s)
161-
await this.updateInstances();
177+
this._pendingUpdateTimer = setTimeout(() => {
178+
this._pendingUpdateTimer = undefined;
179+
void this.runUpdate(updateReason);
180+
}, ComponentViewer.pendingUpdateDelayMs);
162181
}
163182

164-
private async updateInstances(): Promise<void> {
165-
this._instanceUpdateCounter = 0;
183+
private async runUpdate(updateReason: fifoUpdateReason): Promise<void> {
184+
if (this._runningUpdate) {
185+
return;
186+
}
187+
this._runningUpdate = true;
188+
while (this._pendingUpdate) {
189+
this._pendingUpdate = false;
190+
try {
191+
await this.updateInstances(updateReason);
192+
} finally {
193+
this._runningUpdate = false;
194+
logger.error('Component Viewer: Error during update');
195+
}
196+
}
197+
this._runningUpdate = false;
198+
}
199+
200+
private async updateInstances(updateReason: fifoUpdateReason): Promise<void> {
166201
if (!this._activeSession) {
167-
this._componentViewerTreeDataProvider?.deleteModels();
202+
this._componentViewerTreeDataProvider?.clear();
168203
return;
169204
}
205+
logger.debug(`Component Viewer: Queuing update due to '${updateReason}', that is update #${this._updateQueue.length + 1} in the queue`);
206+
this._updateQueue.push({
207+
updateId: this._updateQueue.length + 1,
208+
debugSession: this._activeSession,
209+
updateReason: updateReason
210+
});
211+
this._instanceUpdateCounter = 0;
170212
if (this._instances.length === 0) {
171213
return;
172214
}
173-
this._componentViewerTreeDataProvider?.resetModelCache();
215+
const roots: ScvdGuiInterface[] = [];
174216
for (const instance of this._instances) {
175217
this._instanceUpdateCounter++;
218+
logger.debug(`Updating Component Viewer Instance #${this._instanceUpdateCounter} due to '${updateReason}' (queue position #${this._updateQueue.length})`);
176219
console.log(`Updating Component Viewer Instance #${this._instanceUpdateCounter}`);
177220
await instance.update();
178-
this._componentViewerTreeDataProvider?.addGuiOut(instance.getGuiTree());
221+
const guiTree = instance.getGuiTree();
222+
if (guiTree) {
223+
roots.push(...guiTree);
224+
}
179225
}
180-
this._componentViewerTreeDataProvider?.showModelData();
226+
this._componentViewerTreeDataProvider?.setRoots(roots);
181227
}
182228
}
229+

src/views/component-viewer/component-viewer-target-access.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ export class ComponentViewerTargetAccess {
3232

3333
public async evaluateSymbolAddress(address: string, context = 'hover'): Promise<string | undefined> {
3434
try {
35-
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
35+
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId;
36+
// if FrameId is undefined, evaluation is not possible as cdt-adapter doesn't accept it
37+
if (frameId === undefined) {
38+
return undefined;
39+
}
3640
const args: DebugProtocol.EvaluateArguments = {
3741
expression: `&${address}`,
3842
frameId, // Currently required by CDT GDB Adapter
@@ -71,7 +75,11 @@ export class ComponentViewerTargetAccess {
7175

7276
public async evaluateSymbolName(address: string | number | bigint, context = 'hover'): Promise<string | undefined> {
7377
try {
74-
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
78+
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId;
79+
// if FrameId is undefined, evaluation is not possible as cdt-adapter doesn't accept it
80+
if (frameId === undefined) {
81+
return undefined;
82+
}
7583
const formattedAddress = this.formatAddress(address);
7684
const args: DebugProtocol.EvaluateArguments = {
7785
expression: `(unsigned int*)${formattedAddress}`,
@@ -94,7 +102,11 @@ export class ComponentViewerTargetAccess {
94102

95103
public async evaluateSymbolContext(address: string, context = 'hover'): Promise<string | undefined> {
96104
try {
97-
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
105+
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId;
106+
// if FrameId is undefined, evaluation is not possible as cdt-adapter doesn't accept it
107+
if (frameId === undefined) {
108+
return undefined;
109+
}
98110
const formattedAddress = this.formatAddress(address);
99111
// Ask GDB for file/line context of the address.
100112
const args: DebugProtocol.EvaluateArguments = {
@@ -117,7 +129,11 @@ export class ComponentViewerTargetAccess {
117129

118130
public async evaluateSymbolSize(symbol: string, context = 'hover'): Promise<number | undefined> {
119131
try {
120-
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
132+
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId;
133+
// if FrameId is undefined, evaluation is not possible as cdt-adapter doesn't accept it
134+
if (frameId === undefined) {
135+
return undefined;
136+
}
121137
const args: DebugProtocol.EvaluateArguments = {
122138
expression: `sizeof(${symbol})`,
123139
frameId,
@@ -157,7 +173,11 @@ export class ComponentViewerTargetAccess {
157173

158174
public async evaluateNumberOfArrayElements(symbol: string): Promise<number | undefined> {
159175
try {
160-
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
176+
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId;
177+
// if FrameId is undefined, evaluation is not possible as cdt-adapter doesn't accept it
178+
if (frameId === undefined) {
179+
return undefined;
180+
}
161181
const args: DebugProtocol.EvaluateArguments = {
162182
expression: `sizeof(${symbol})/sizeof(${symbol}[0])`,
163183
frameId, // Currently required by CDT GDB Adapter
@@ -179,7 +199,11 @@ export class ComponentViewerTargetAccess {
179199

180200
public async evaluateRegisterValue(register: string): Promise<string | undefined> {
181201
try {
182-
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
202+
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId;
203+
// if FrameId is undefined, evaluation is not possible as cdt-adapter doesn't accept it
204+
if (frameId === undefined) {
205+
return undefined;
206+
}
183207
const args: DebugProtocol.EvaluateArguments = {
184208
expression: `$${register}`,
185209
frameId, // Currently required by CDT GDB Adapter

0 commit comments

Comments
 (0)