Skip to content

Commit d6a4b0f

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
fix: update monitor settings only if it's changed
Closes #375 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
1 parent c0488d1 commit d6a4b0f

File tree

3 files changed

+131
-1
lines changed

3 files changed

+131
-1
lines changed

arduino-ide-extension/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"google-protobuf": "^3.20.1",
7474
"hash.js": "^1.1.7",
7575
"js-yaml": "^3.13.1",
76+
"just-diff": "^5.1.1",
7677
"jwt-decode": "^3.1.2",
7778
"keytar": "7.2.0",
7879
"lodash.debounce": "^4.0.8",

arduino-ide-extension/src/node/monitor-service.ts

+125-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ClientDuplexStream } from '@grpc/grpc-js';
22
import { Disposable, Emitter, ILogger } from '@theia/core';
33
import { inject, named, postConstruct } from '@theia/core/shared/inversify';
4+
import { diff, Operation } from 'just-diff';
45
import { Board, Port, Status, Monitor } from '../common/protocol';
56
import {
67
EnumerateMonitorPortSettingsRequest,
@@ -80,6 +81,15 @@ export class MonitorService extends CoreClientAware implements Disposable {
8081
private readonly port: Port;
8182
private readonly monitorID: string;
8283

84+
/**
85+
* The lightweight representation of the port configuration currently in use for the running monitor.
86+
* IDE2 stores this object after starting the monitor. On every monitor settings change request, IDE2 compares
87+
* the current config with the new settings, and only sends the diff as the new config to overcome https://github.com/arduino/arduino-ide/issues/375.
88+
*/
89+
private currentPortConfigSnapshot:
90+
| MonitorPortConfiguration.AsObject
91+
| undefined;
92+
8393
constructor(
8494
@inject(MonitorServiceFactoryOptions) options: MonitorServiceFactoryOptions
8595
) {
@@ -211,6 +221,16 @@ export class MonitorService extends CoreClientAware implements Disposable {
211221
monitorRequest
212222
);
213223
if (wroteToStreamSuccessfully) {
224+
// Only store the config, if the monitor has successfully started.
225+
this.currentPortConfigSnapshot = MonitorPortConfiguration.toObject(
226+
false,
227+
config
228+
);
229+
this.logger.info(
230+
`Using port configuration for ${this.port.protocol}:${
231+
this.port.address
232+
}: ${JSON.stringify(this.currentPortConfigSnapshot)}`
233+
);
214234
this.startMessagesHandlers();
215235
this.logger.info(
216236
`started monitor to ${this.port?.address} using ${this.port?.protocol}`
@@ -518,16 +538,120 @@ export class MonitorService extends CoreClientAware implements Disposable {
518538
if (!this.duplex) {
519539
return Status.NOT_CONNECTED;
520540
}
541+
542+
const diffConfig = this.maybeUpdatePortConfigSnapshot(config);
543+
if (!diffConfig) {
544+
this.logger.info(
545+
`No port configuration changes have been detected. No need to send configure commands to the running monitor ${this.port.protocol}:${this.port.address}.`
546+
);
547+
return Status.OK;
548+
}
549+
521550
const coreClient = await this.coreClient;
522551
const { instance } = coreClient;
523552

553+
this.logger.info(
554+
`Sending monitor request with new port configuration: ${JSON.stringify(
555+
MonitorPortConfiguration.toObject(false, diffConfig)
556+
)}`
557+
);
524558
const req = new MonitorRequest();
525559
req.setInstance(instance);
526-
req.setPortConfiguration(config);
560+
req.setPortConfiguration(diffConfig);
527561
this.duplex.write(req);
528562
return Status.OK;
529563
}
530564

565+
/**
566+
* Function to calculate a diff between the `otherPortConfig` argument and the `currentPortConfigSnapshot`.
567+
*
568+
* If the current config snapshot and the snapshot derived from `otherPortConfig` are the same, no snapshot update happens,
569+
* and the function returns with undefined. Otherwise, the current snapshot config value will be updated from the snapshot
570+
* derived from the `otherPortConfig` argument, and this function returns with a `MonitorPortConfiguration` instance
571+
* representing only the difference between the two snapshot configs to avoid sending unnecessary monitor to configure commands to the CLI.
572+
* See [#1703 (comment)](https://github.com/arduino/arduino-ide/pull/1703#issuecomment-1327913005) for more details.
573+
*/
574+
private maybeUpdatePortConfigSnapshot(
575+
otherPortConfig: MonitorPortConfiguration
576+
): MonitorPortConfiguration | undefined {
577+
const otherPortConfigSnapshot = MonitorPortConfiguration.toObject(
578+
false,
579+
otherPortConfig
580+
);
581+
if (!this.currentPortConfigSnapshot) {
582+
throw new Error(
583+
`The current port configuration object was undefined when tried to merge in ${JSON.stringify(
584+
otherPortConfigSnapshot
585+
)}.`
586+
);
587+
}
588+
589+
const snapshotDiff = diff(
590+
this.currentPortConfigSnapshot,
591+
otherPortConfigSnapshot
592+
);
593+
if (!snapshotDiff.length) {
594+
return undefined;
595+
}
596+
597+
const diffConfig = snapshotDiff.reduce((acc, curr) => {
598+
if (!this.isValidMonitorPortSettingChange(curr)) {
599+
throw new Error(
600+
`Expected only 'replace' operation: a 'value' change in the 'settingsList'. Calculated diff a ${JSON.stringify(
601+
snapshotDiff
602+
)} between ${JSON.stringify(
603+
this.currentPortConfigSnapshot
604+
)} and ${JSON.stringify(
605+
otherPortConfigSnapshot
606+
)} snapshots. Current JSON-patch entry was ${JSON.stringify(curr)}.`
607+
);
608+
}
609+
const { path, value } = curr;
610+
const [, index] = path;
611+
if (!this.currentPortConfigSnapshot?.settingsList) {
612+
throw new Error(
613+
`'settingsList' is missing from current port config snapshot: ${JSON.stringify(
614+
this.currentPortConfigSnapshot
615+
)}`
616+
);
617+
}
618+
const changedSetting = this.currentPortConfigSnapshot.settingsList[index];
619+
const setting = new MonitorPortSetting();
620+
setting.setValue(value);
621+
setting.setSettingId(changedSetting.settingId);
622+
acc.addSettings(setting);
623+
return acc;
624+
}, new MonitorPortConfiguration());
625+
626+
this.currentPortConfigSnapshot = otherPortConfigSnapshot;
627+
this.logger.info(
628+
`Updated the port configuration for ${this.port.protocol}:${
629+
this.port.address
630+
}: ${JSON.stringify(this.currentPortConfigSnapshot)}`
631+
);
632+
return diffConfig;
633+
}
634+
635+
private isValidMonitorPortSettingChange(entry: {
636+
op: Operation;
637+
path: (string | number)[];
638+
value: unknown;
639+
}): entry is {
640+
op: 'replace';
641+
path: ['settingsList', number, string];
642+
value: string;
643+
} {
644+
const { op, path, value } = entry;
645+
return (
646+
op === 'replace' &&
647+
path.length === 3 &&
648+
path[0] === 'settingsList' &&
649+
typeof path[1] === 'number' &&
650+
path[2] === 'value' &&
651+
typeof value === 'string'
652+
);
653+
}
654+
531655
/**
532656
* Starts the necessary handlers to send and receive
533657
* messages to and from the frontend and the running monitor

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -9391,6 +9391,11 @@ jsprim@^1.2.2:
93919391
array-includes "^3.1.5"
93929392
object.assign "^4.1.2"
93939393

9394+
just-diff@^5.1.1:
9395+
version "5.1.1"
9396+
resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.1.1.tgz#8da6414342a5ed6d02ccd64f5586cbbed3146202"
9397+
integrity sha512-u8HXJ3HlNrTzY7zrYYKjNEfBlyjqhdBkoyTVdjtn7p02RJD5NvR8rIClzeGA7t+UYP1/7eAkWNLU0+P3QrEqKQ==
9398+
93949399
jwt-decode@^3.1.2:
93959400
version "3.1.2"
93969401
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"

0 commit comments

Comments
 (0)