mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-18 14:49:27 +00:00
Speed up IDE startup time.
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
64
arduino-ide-extension/src/common/decorators.ts
Normal file
64
arduino-ide-extension/src/common/decorators.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import { default as stringifySafe } from 'fast-safe-stringify';
|
||||
|
||||
export interface DurationOptions {
|
||||
/**
|
||||
* If not specified, falls back to the `String()` value of the `PropertyKey`.
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* If the duration exceeds this timeout (in millis), then the duration will be logged as an error.
|
||||
*/
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export function duration(options?: DurationOptions) {
|
||||
return (
|
||||
_target: unknown,
|
||||
key: PropertyKey,
|
||||
descriptor: PropertyDescriptor
|
||||
): PropertyDescriptor => {
|
||||
const original = descriptor.value;
|
||||
descriptor.value = async function (...args: unknown[]) {
|
||||
const input = args
|
||||
.filter((arg) => !Boolean(isCancellationToken(arg)))
|
||||
.map(stringify)
|
||||
.join(',');
|
||||
const start = performance.now();
|
||||
const result = await original.apply(this, args);
|
||||
const end = performance.now();
|
||||
const duration = end - start;
|
||||
const slow = duration > (options?.timeout ?? 100);
|
||||
const message = `---- ${slow ? '!!!SLOW!!! ' : ''}DURATION: ${
|
||||
options?.name ?? String(key)
|
||||
} took ${duration.toFixed(3)} ms. Args: [${input}] ----`;
|
||||
if (slow) {
|
||||
console.error(message);
|
||||
} else {
|
||||
console.info(message);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
function stringify(arg: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(arg);
|
||||
} catch {
|
||||
return stringifySafe(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// The cancellation token is implicitly the last arg of the JSON-RPC invocation. We want to filter it out from the logs.
|
||||
// See: https://github.com/eclipse-theia/theia/issues/10129
|
||||
function isCancellationToken(arg: unknown): arg is CancellationToken {
|
||||
return (
|
||||
typeof arg === 'object' &&
|
||||
arg !== null &&
|
||||
'onCancellationRequested' in arg &&
|
||||
'isCancellationRequested' in arg
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,15 @@
|
||||
export const ArduinoDaemonPath = '/services/arduino-daemon';
|
||||
export const ArduinoDaemon = Symbol('ArduinoDaemon');
|
||||
export interface ArduinoDaemon {
|
||||
isRunning(): Promise<boolean>;
|
||||
/**
|
||||
* Returns with a promise that resolves with the port
|
||||
* of the CLI daemon when it's up and running.
|
||||
*/
|
||||
getPort(): Promise<string>;
|
||||
/**
|
||||
* Unlike `getPort` this method returns with a promise
|
||||
* that resolves to `undefined` when the daemon is not running.
|
||||
* Otherwise resolves to the CLI daemon port.
|
||||
*/
|
||||
tryGetPort(): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ export interface ConfigService {
|
||||
getCliConfigFileUri(): Promise<string>;
|
||||
getConfiguration(): Promise<Config>;
|
||||
setConfiguration(config: Config): Promise<void>;
|
||||
isInDataDir(uri: string): Promise<boolean>;
|
||||
isInSketchDir(uri: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface Daemon {
|
||||
@@ -115,10 +113,8 @@ export interface Config {
|
||||
readonly locale: string;
|
||||
readonly sketchDirUri: string;
|
||||
readonly dataDirUri: string;
|
||||
readonly downloadsDirUri: string;
|
||||
readonly additionalUrls: AdditionalUrls;
|
||||
readonly network: Network;
|
||||
readonly daemon: Daemon;
|
||||
}
|
||||
export namespace Config {
|
||||
export function sameAs(left: Config, right: Config): boolean {
|
||||
@@ -135,7 +131,6 @@ export namespace Config {
|
||||
return (
|
||||
left.locale === right.locale &&
|
||||
left.dataDirUri === right.dataDirUri &&
|
||||
left.downloadsDirUri === right.downloadsDirUri &&
|
||||
left.sketchDirUri === right.sketchDirUri &&
|
||||
Network.sameAs(left.network, right.network)
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
|
||||
export interface NotificationServiceClient {
|
||||
notifyIndexUpdated(): void;
|
||||
notifyDaemonStarted(): void;
|
||||
notifyDaemonStarted(port: string): void;
|
||||
notifyDaemonStopped(): void;
|
||||
notifyConfigChanged(event: { config: Config | undefined }): void;
|
||||
notifyPlatformInstalled(event: { item: BoardsPackage }): void;
|
||||
|
||||
@@ -10,16 +10,24 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { Sketch, SketchesService } from '../../common/protocol';
|
||||
import { ConfigService } from './config-service';
|
||||
import { SketchContainer } from './sketches-service';
|
||||
import { SketchContainer, SketchRef } from './sketches-service';
|
||||
import {
|
||||
ARDUINO_CLOUD_FOLDER,
|
||||
REMOTE_SKETCHBOOK_FOLDER,
|
||||
} from '../../browser/utils/constants';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
|
||||
const READ_ONLY_FILES = ['sketch.json'];
|
||||
const READ_ONLY_FILES_REMOTE = ['thingProperties.h', 'thingsProperties.h'];
|
||||
|
||||
export type CurrentSketch = Sketch | 'invalid';
|
||||
export namespace CurrentSketch {
|
||||
export function isValid(arg: CurrentSketch | undefined): arg is Sketch {
|
||||
return !!arg && arg !== 'invalid';
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SketchesServiceClientImpl
|
||||
implements FrontendApplicationContribution
|
||||
@@ -40,13 +48,16 @@ export class SketchesServiceClientImpl
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
protected toDispose = new DisposableCollection();
|
||||
protected sketches = new Map<string, Sketch>();
|
||||
protected sketches = new Map<string, SketchRef>();
|
||||
// TODO: rename this + event to the `onBlabla` pattern
|
||||
protected sketchbookDidChangeEmitter = new Emitter<{
|
||||
created: Sketch[];
|
||||
removed: Sketch[];
|
||||
created: SketchRef[];
|
||||
removed: SketchRef[];
|
||||
}>();
|
||||
readonly onSketchbookDidChange = this.sketchbookDidChangeEmitter.event;
|
||||
|
||||
private _currentSketch = new Deferred<CurrentSketch>();
|
||||
|
||||
onStart(): void {
|
||||
this.configService.getConfiguration().then(({ sketchDirUri }) => {
|
||||
this.sketchService
|
||||
@@ -99,13 +110,16 @@ export class SketchesServiceClientImpl
|
||||
);
|
||||
});
|
||||
});
|
||||
this.loadCurrentSketch().then((currentSketch) =>
|
||||
this._currentSketch.resolve(currentSketch)
|
||||
);
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
async currentSketch(): Promise<Sketch | undefined> {
|
||||
private async loadCurrentSketch(): Promise<CurrentSketch> {
|
||||
const sketches = (
|
||||
await Promise.all(
|
||||
this.workspaceService
|
||||
@@ -116,7 +130,7 @@ export class SketchesServiceClientImpl
|
||||
)
|
||||
).filter(notEmpty);
|
||||
if (!sketches.length) {
|
||||
return undefined;
|
||||
return 'invalid';
|
||||
}
|
||||
if (sketches.length > 1) {
|
||||
console.log(
|
||||
@@ -128,16 +142,14 @@ export class SketchesServiceClientImpl
|
||||
return sketches[0];
|
||||
}
|
||||
|
||||
async currentSketch(): Promise<CurrentSketch> {
|
||||
return this._currentSketch.promise;
|
||||
}
|
||||
|
||||
async currentSketchFile(): Promise<string | undefined> {
|
||||
const sketch = await this.currentSketch();
|
||||
if (sketch) {
|
||||
const uri = sketch.mainFileUri;
|
||||
const exists = await this.fileService.exists(new URI(uri));
|
||||
if (!exists) {
|
||||
this.messageService.warn(`Could not find sketch file: ${uri}`);
|
||||
return undefined;
|
||||
}
|
||||
return uri;
|
||||
const currentSketch = await this.currentSketch();
|
||||
if (CurrentSketch.isValid(currentSketch)) {
|
||||
return currentSketch.mainFileUri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -145,10 +157,10 @@ export class SketchesServiceClientImpl
|
||||
private fireSoonHandle?: number;
|
||||
private bufferedSketchbookEvents: {
|
||||
type: 'created' | 'removed';
|
||||
sketch: Sketch;
|
||||
sketch: SketchRef;
|
||||
}[] = [];
|
||||
|
||||
private fireSoon(sketch: Sketch, type: 'created' | 'removed'): void {
|
||||
private fireSoon(sketch: SketchRef, type: 'created' | 'removed'): void {
|
||||
this.bufferedSketchbookEvents.push({ type, sketch });
|
||||
|
||||
if (typeof this.fireSoonHandle === 'number') {
|
||||
@@ -156,7 +168,7 @@ export class SketchesServiceClientImpl
|
||||
}
|
||||
|
||||
this.fireSoonHandle = window.setTimeout(() => {
|
||||
const event: { created: Sketch[]; removed: Sketch[] } = {
|
||||
const event: { created: SketchRef[]; removed: SketchRef[] } = {
|
||||
created: [],
|
||||
removed: [],
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface SketchesService {
|
||||
* Sketches are created to the temp location by default and will be moved under `directories.user` on save.
|
||||
* This method resolves to `true` if the `sketch` is still in the temp location. Otherwise, `false`.
|
||||
*/
|
||||
isTemp(sketch: Sketch): Promise<boolean>;
|
||||
isTemp(sketch: SketchRef): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* If `isTemp` is `true` for the `sketch`, you can call this method to move the sketch from the temp
|
||||
@@ -81,9 +81,20 @@ export interface SketchesService {
|
||||
getIdeTempFolderUri(sketch: Sketch): Promise<string>;
|
||||
}
|
||||
|
||||
export interface Sketch {
|
||||
export interface SketchRef {
|
||||
readonly name: string;
|
||||
readonly uri: string; // `LocationPath`
|
||||
}
|
||||
export namespace SketchRef {
|
||||
export function fromUri(uriLike: string | URI): SketchRef {
|
||||
const uri = typeof uriLike === 'string' ? new URI(uriLike) : uriLike;
|
||||
return {
|
||||
name: uri.path.base,
|
||||
uri: typeof uriLike === 'string' ? uriLike : uriLike.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
export interface Sketch extends SketchRef {
|
||||
readonly mainFileUri: string; // `MainFile`
|
||||
readonly otherSketchFileUris: string[]; // `OtherSketchFiles`
|
||||
readonly additionalFileUris: string[]; // `AdditionalFiles`
|
||||
@@ -134,9 +145,16 @@ export namespace Sketch {
|
||||
export interface SketchContainer {
|
||||
readonly label: string;
|
||||
readonly children: SketchContainer[];
|
||||
readonly sketches: Sketch[];
|
||||
readonly sketches: SketchRef[];
|
||||
}
|
||||
export namespace SketchContainer {
|
||||
export function create(label: string): SketchContainer {
|
||||
return {
|
||||
label,
|
||||
children: [],
|
||||
sketches: [],
|
||||
};
|
||||
}
|
||||
export function is(arg: any): arg is SketchContainer {
|
||||
return (
|
||||
!!arg &&
|
||||
@@ -174,8 +192,8 @@ export namespace SketchContainer {
|
||||
return container;
|
||||
}
|
||||
|
||||
export function toArray(container: SketchContainer): Sketch[] {
|
||||
const visit = (parent: SketchContainer, toPushSketch: Sketch[]) => {
|
||||
export function toArray(container: SketchContainer): SketchRef[] {
|
||||
const visit = (parent: SketchContainer, toPushSketch: SketchRef[]) => {
|
||||
toPushSketch.push(...parent.sketches);
|
||||
parent.children.map((child) => visit(child, toPushSketch));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user