sen-ether-client 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +44 -17
- package/lib/sen.js +247 -48
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -103,6 +103,25 @@ function resolveInterfaceAddress(value) {
|
|
|
103
103
|
return ipv4.address;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
function multicastInterfaceCandidates(interfaceAddress) {
|
|
107
|
+
if (interfaceAddress) {
|
|
108
|
+
return [interfaceAddress];
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const addresses = [];
|
|
112
|
+
for (const candidates of Object.values(os.networkInterfaces())) {
|
|
113
|
+
for (const item of candidates ?? []) {
|
|
114
|
+
if ((item.family === 'IPv4' || item.family === 4) && !item.internal && item.address) {
|
|
115
|
+
addresses.push(item.address);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return [...new Set(addresses)];
|
|
120
|
+
} catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
function normalizeMulticastRange(value) {
|
|
107
126
|
const ranges = Array.isArray(value) && value.length === 4 ? value : DEFAULT_MULTICAST_RANGE;
|
|
108
127
|
return ranges.map((range, index) => {
|
|
@@ -237,6 +256,9 @@ export class EtherClient extends EventEmitter {
|
|
|
237
256
|
busMulticastRange: DEFAULT_MULTICAST_RANGE,
|
|
238
257
|
...options
|
|
239
258
|
};
|
|
259
|
+
this.options.discoveryPort ??= discoveryPortFromEnv() ?? DEFAULT_DISCOVERY_PORT;
|
|
260
|
+
this.options.busMulticastPort ??= DEFAULT_BUS_MULTICAST_PORT;
|
|
261
|
+
this.options.busMulticastRange ??= DEFAULT_MULTICAST_RANGE;
|
|
240
262
|
this.processInfo = createProcessInfo(this.options);
|
|
241
263
|
this.interfaceAddress = resolveInterfaceAddress(this.options.interfaceAddress);
|
|
242
264
|
this.busMulticastRange = normalizeMulticastRange(this.options.busMulticastRange);
|
|
@@ -432,6 +454,15 @@ export class EtherClient extends EventEmitter {
|
|
|
432
454
|
return { busName: busState.busName, busId: busState.busId, id, query };
|
|
433
455
|
}
|
|
434
456
|
|
|
457
|
+
#restartInterestForRemote(busState) {
|
|
458
|
+
for (const interest of busState.interests.values()) {
|
|
459
|
+
this.#sendBusControl(busState, busState.participantId, {
|
|
460
|
+
type: 'InterestStarted',
|
|
461
|
+
value: { query: interest.query, id: interest.id }
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
435
466
|
/**
|
|
436
467
|
* Stop a previously started interest.
|
|
437
468
|
*
|
|
@@ -708,25 +739,13 @@ export class EtherClient extends EventEmitter {
|
|
|
708
739
|
|
|
709
740
|
#onMulticastBusDatagram(busState, message, remote) {
|
|
710
741
|
try {
|
|
711
|
-
if (message.length <
|
|
742
|
+
if (message.length < 9) {
|
|
712
743
|
throw new RangeError(`SEN multicast bus datagram too small: ${message.length}`);
|
|
713
744
|
}
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
if (processId === this.processInfo.processId) {
|
|
745
|
+
const frame = decodeConfirmedBusFrame(message);
|
|
746
|
+
if (frame.to !== busState.participantId) {
|
|
717
747
|
return;
|
|
718
748
|
}
|
|
719
|
-
if (payloadSize !== message.length - 8) {
|
|
720
|
-
throw new RangeError(
|
|
721
|
-
`SEN multicast bus payload size mismatch: expected ${payloadSize}, got ${message.length - 8}`
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
const frame = {
|
|
726
|
-
to: busState.participantId,
|
|
727
|
-
busId: busState.busId,
|
|
728
|
-
message: message.subarray(8)
|
|
729
|
-
};
|
|
730
749
|
const busMessage = decodeBusMessage(frame.message);
|
|
731
750
|
this.emit('busFrame', { ...frame, busMessage, remote, multicast: true });
|
|
732
751
|
if (busMessage.categoryName === 'controlMessage') {
|
|
@@ -763,7 +782,14 @@ export class EtherClient extends EventEmitter {
|
|
|
763
782
|
const onListening = () => {
|
|
764
783
|
socket.off('error', onError);
|
|
765
784
|
try {
|
|
766
|
-
|
|
785
|
+
const interfaces = multicastInterfaceCandidates(this.interfaceAddress);
|
|
786
|
+
if (interfaces.length) {
|
|
787
|
+
for (const interfaceAddress of interfaces) {
|
|
788
|
+
socket.addMembership(group, interfaceAddress);
|
|
789
|
+
}
|
|
790
|
+
} else {
|
|
791
|
+
socket.addMembership(group);
|
|
792
|
+
}
|
|
767
793
|
socket.setMulticastLoopback(true);
|
|
768
794
|
if (this.interfaceAddress) {
|
|
769
795
|
socket.setMulticastInterface(this.interfaceAddress);
|
|
@@ -786,13 +812,14 @@ export class EtherClient extends EventEmitter {
|
|
|
786
812
|
return;
|
|
787
813
|
}
|
|
788
814
|
|
|
789
|
-
const remoteParticipantId = frame.to;
|
|
815
|
+
const remoteParticipantId = frame.to >>> 0;
|
|
790
816
|
if (!busState.readyRemoteParticipants.has(remoteParticipantId)) {
|
|
791
817
|
busState.readyRemoteParticipants.add(remoteParticipantId);
|
|
792
818
|
this.#sendBusControl(busState, busState.participantId, {
|
|
793
819
|
type: 'RemoteParticipantReady',
|
|
794
820
|
value: { id: remoteParticipantId }
|
|
795
821
|
});
|
|
822
|
+
this.#restartInterestForRemote(busState);
|
|
796
823
|
this.emit('busParticipantReady', {
|
|
797
824
|
busName: busState.busName,
|
|
798
825
|
busId: busState.busId,
|
package/lib/sen.js
CHANGED
|
@@ -9,6 +9,9 @@ function wait(ms) {
|
|
|
9
9
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
const STATE_RESYNC_DELAYS_MS = [250, 1000, 3000, 8000];
|
|
13
|
+
const STATE_RESYNC_INTERVAL_MS = 1000;
|
|
14
|
+
|
|
12
15
|
async function waitForEvent(emitter, event, timeoutMs) {
|
|
13
16
|
let timeoutId;
|
|
14
17
|
const timeout = new Promise((_, reject) => {
|
|
@@ -782,6 +785,13 @@ export class Sen extends EventEmitter {
|
|
|
782
785
|
return target;
|
|
783
786
|
}
|
|
784
787
|
|
|
788
|
+
async #reconnectTarget(options) {
|
|
789
|
+
if (options.tcpHub || !options.target) {
|
|
790
|
+
return await this.#discoverTarget(options);
|
|
791
|
+
}
|
|
792
|
+
return options.target;
|
|
793
|
+
}
|
|
794
|
+
|
|
785
795
|
#sessionNameForBus(busName, options = {}) {
|
|
786
796
|
const explicit = String(options.session || '').trim();
|
|
787
797
|
if (explicit) {
|
|
@@ -899,7 +909,7 @@ export class Sen extends EventEmitter {
|
|
|
899
909
|
}
|
|
900
910
|
|
|
901
911
|
const config = this.connectOptions;
|
|
902
|
-
const target =
|
|
912
|
+
const target = await this.#reconnectTarget(config);
|
|
903
913
|
if (!target) {
|
|
904
914
|
throw new Error('no SEN ether process matches the requested filters');
|
|
905
915
|
}
|
|
@@ -1062,6 +1072,8 @@ export class SenBus extends EventEmitter {
|
|
|
1062
1072
|
this.typeRegistry = new Map();
|
|
1063
1073
|
this.requestedTypeHashes = new Set();
|
|
1064
1074
|
this.stateRequestedObjectIds = new Set();
|
|
1075
|
+
this.stateResyncTimers = new Set();
|
|
1076
|
+
this.stateResyncInterval = undefined;
|
|
1065
1077
|
this.interests = new Map();
|
|
1066
1078
|
this.pendingCalls = new Map();
|
|
1067
1079
|
this.nextTicketId = 1;
|
|
@@ -1076,10 +1088,13 @@ export class SenBus extends EventEmitter {
|
|
|
1076
1088
|
|
|
1077
1089
|
stopInterest(id) {
|
|
1078
1090
|
const interestId = typeof id === 'object' ? id.id : id;
|
|
1079
|
-
this.sen.client
|
|
1091
|
+
this.sen.client?.stopInterest(this.name, interestId);
|
|
1080
1092
|
const interest = this.interests.get(interestId);
|
|
1081
1093
|
this.#detachInterestObjects(interestId, interest);
|
|
1082
1094
|
this.interests.delete(interestId);
|
|
1095
|
+
if (!this.interests.size) {
|
|
1096
|
+
this.#clearStateResyncTimers();
|
|
1097
|
+
}
|
|
1083
1098
|
interest?.closeLocal();
|
|
1084
1099
|
interest?.emit('close');
|
|
1085
1100
|
}
|
|
@@ -1097,7 +1112,7 @@ export class SenBus extends EventEmitter {
|
|
|
1097
1112
|
}
|
|
1098
1113
|
}
|
|
1099
1114
|
try {
|
|
1100
|
-
this.sen.client
|
|
1115
|
+
this.sen.client?.leaveBus(this.name);
|
|
1101
1116
|
} catch (error) {
|
|
1102
1117
|
this.sen.emit('warning', error);
|
|
1103
1118
|
}
|
|
@@ -1117,6 +1132,7 @@ export class SenBus extends EventEmitter {
|
|
|
1117
1132
|
this.typeRegistry.clear();
|
|
1118
1133
|
this.requestedTypeHashes.clear();
|
|
1119
1134
|
this.stateRequestedObjectIds.clear();
|
|
1135
|
+
this.#clearStateResyncTimers();
|
|
1120
1136
|
for (const interest of this.interests.values()) {
|
|
1121
1137
|
interest.closeLocal();
|
|
1122
1138
|
interest.objectsById.clear();
|
|
@@ -1158,8 +1174,25 @@ export class SenBus extends EventEmitter {
|
|
|
1158
1174
|
handleObjectsPublished(event) {
|
|
1159
1175
|
const newTypeHashes = new Set();
|
|
1160
1176
|
for (const discovery of event.discoveries ?? []) {
|
|
1177
|
+
const interest = this.interests.get(discovery.interestId);
|
|
1178
|
+
if (interest && event.ownerId !== undefined) {
|
|
1179
|
+
if (interest.ownerId !== undefined && interest.ownerId !== event.ownerId) {
|
|
1180
|
+
this.#resetInterestForOwner(interest, event.ownerId);
|
|
1181
|
+
} else {
|
|
1182
|
+
interest.ownerId = event.ownerId;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1161
1186
|
for (const info of discovery.objects ?? []) {
|
|
1162
1187
|
let object = this.objectsById.get(info.id);
|
|
1188
|
+
if (object && event.ownerId !== undefined && object.ownerId !== event.ownerId) {
|
|
1189
|
+
this.#removeObjectFromAllInterests(object, {
|
|
1190
|
+
reason: 'ownerChanged',
|
|
1191
|
+
ownerId: event.ownerId,
|
|
1192
|
+
previousOwnerId: object.ownerId
|
|
1193
|
+
});
|
|
1194
|
+
object = undefined;
|
|
1195
|
+
}
|
|
1163
1196
|
const isNewObject = !object;
|
|
1164
1197
|
if (!object) {
|
|
1165
1198
|
object = new SenRemoteObject(this, {
|
|
@@ -1175,26 +1208,22 @@ export class SenBus extends EventEmitter {
|
|
|
1175
1208
|
ownerId: event.ownerId
|
|
1176
1209
|
});
|
|
1177
1210
|
}
|
|
1178
|
-
const interest = this.interests.get(discovery.interestId);
|
|
1179
1211
|
interest?.objectsById.set(object.id, object);
|
|
1180
1212
|
if (info.state?.length) {
|
|
1181
|
-
object.applyState(info.state, '
|
|
1213
|
+
object.applyState(info.state, 'published', info.time, { interestId: discovery.interestId });
|
|
1182
1214
|
}
|
|
1183
1215
|
if (!this.requestedTypeHashes.has(info.typeHash)) {
|
|
1184
1216
|
this.requestedTypeHashes.add(info.typeHash);
|
|
1185
1217
|
newTypeHashes.add(info.typeHash);
|
|
1186
1218
|
}
|
|
1187
|
-
interest
|
|
1188
|
-
if (isNewObject) {
|
|
1189
|
-
this.emit('object', object);
|
|
1190
|
-
this.sen.emit('object', object);
|
|
1191
|
-
}
|
|
1219
|
+
this.#emitObjectWhenReady(interest, object, isNewObject);
|
|
1192
1220
|
}
|
|
1193
1221
|
}
|
|
1194
1222
|
if (newTypeHashes.size) {
|
|
1195
1223
|
this.sen.client.requestTypes(this.name, newTypeHashes);
|
|
1196
1224
|
}
|
|
1197
1225
|
this.#requestReadyObjectStates();
|
|
1226
|
+
this.#scheduleStateResyncs();
|
|
1198
1227
|
}
|
|
1199
1228
|
|
|
1200
1229
|
handleObjectsRemoved(event) {
|
|
@@ -1202,18 +1231,8 @@ export class SenBus extends EventEmitter {
|
|
|
1202
1231
|
for (const id of removal.ids ?? []) {
|
|
1203
1232
|
const object = this.objectsById.get(id);
|
|
1204
1233
|
const interest = this.interests.get(removal.interestId);
|
|
1205
|
-
interest?.objectsById.delete(id);
|
|
1206
|
-
this.stateRequestedObjectIds.delete(stateRequestKey(removal.interestId, id));
|
|
1207
1234
|
if (object) {
|
|
1208
|
-
object
|
|
1209
|
-
object.emit('remove', { interestId: removal.interestId });
|
|
1210
|
-
interest?.emit('remove', object);
|
|
1211
|
-
if (object.interestIds.size === 0) {
|
|
1212
|
-
this.objectsById.delete(id);
|
|
1213
|
-
this.requestedTypeHashes.delete(object.typeHash);
|
|
1214
|
-
this.emit('remove', object);
|
|
1215
|
-
this.sen.emit('remove', object);
|
|
1216
|
-
}
|
|
1235
|
+
this.#removeObjectFromInterest(object, removal.interestId, interest);
|
|
1217
1236
|
}
|
|
1218
1237
|
}
|
|
1219
1238
|
}
|
|
@@ -1233,13 +1252,74 @@ export class SenBus extends EventEmitter {
|
|
|
1233
1252
|
}
|
|
1234
1253
|
|
|
1235
1254
|
for (const object of interest.objectsById.values()) {
|
|
1236
|
-
object
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1255
|
+
this.#removeObjectFromInterest(object, normalizedInterestId, interest);
|
|
1256
|
+
}
|
|
1257
|
+
interest.objectsById.clear();
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
#resetInterestForOwner(interest, ownerId) {
|
|
1261
|
+
const previousOwnerId = interest.ownerId;
|
|
1262
|
+
const detail = { reason: 'ownerChanged', ownerId, previousOwnerId };
|
|
1263
|
+
for (const object of [...interest.objectsById.values()]) {
|
|
1264
|
+
this.#removeObjectFromAllInterests(object, detail);
|
|
1241
1265
|
}
|
|
1242
1266
|
interest.objectsById.clear();
|
|
1267
|
+
interest.ownerId = ownerId;
|
|
1268
|
+
interest.resetLocal();
|
|
1269
|
+
interest.emit('stale', detail);
|
|
1270
|
+
this.emit('stale', { interest, ...detail });
|
|
1271
|
+
this.sen.emit('stale', { bus: this, interest, ...detail });
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
#removeObjectFromAllInterests(object, detail = {}) {
|
|
1275
|
+
for (const interestId of [...object.interestIds]) {
|
|
1276
|
+
this.#removeObjectFromInterest(object, interestId, this.interests.get(interestId), detail);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
#removeObjectFromInterest(object, interestId, interest, detail = {}) {
|
|
1281
|
+
const normalizedInterestId = interestId >>> 0;
|
|
1282
|
+
this.stateRequestedObjectIds.delete(stateRequestKey(normalizedInterestId, object.id));
|
|
1283
|
+
interest?.objectsById.delete(object.id);
|
|
1284
|
+
object.detachInterest(normalizedInterestId);
|
|
1285
|
+
object.emit('remove', { interestId: normalizedInterestId, ...detail });
|
|
1286
|
+
interest?.emit('remove', object);
|
|
1287
|
+
if (object.interestIds.size === 0) {
|
|
1288
|
+
this.objectsById.delete(object.id);
|
|
1289
|
+
this.requestedTypeHashes.delete(object.typeHash);
|
|
1290
|
+
this.emit('remove', object);
|
|
1291
|
+
this.sen.emit('remove', object);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
#emitObjectWhenReady(interest, object, emitGlobal) {
|
|
1296
|
+
if (!interest) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const publish = () => {
|
|
1300
|
+
if (!object.isReadyForInterest(interest.id)) {
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
if (object.markInterestObjectEmitted(interest.id)) {
|
|
1304
|
+
interest.emit('object', object);
|
|
1305
|
+
}
|
|
1306
|
+
if (emitGlobal && object.markGlobalObjectEmitted()) {
|
|
1307
|
+
this.emit('object', object);
|
|
1308
|
+
this.sen.emit('object', object);
|
|
1309
|
+
}
|
|
1310
|
+
return true;
|
|
1311
|
+
};
|
|
1312
|
+
|
|
1313
|
+
if (publish()) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const onReady = () => {
|
|
1318
|
+
if (publish()) {
|
|
1319
|
+
object.off('ready', onReady);
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
object.on('ready', onReady);
|
|
1243
1323
|
}
|
|
1244
1324
|
|
|
1245
1325
|
handleTypesInfoResponse(event) {
|
|
@@ -1267,13 +1347,14 @@ export class SenBus extends EventEmitter {
|
|
|
1267
1347
|
}
|
|
1268
1348
|
this.#retryPendingStates();
|
|
1269
1349
|
this.#requestReadyObjectStates();
|
|
1350
|
+
this.#scheduleStateResyncs();
|
|
1270
1351
|
}
|
|
1271
1352
|
|
|
1272
1353
|
handleObjectsStateResponse(event) {
|
|
1273
1354
|
for (const response of event.responses ?? []) {
|
|
1274
1355
|
for (const state of response.objectStates ?? []) {
|
|
1275
1356
|
const object = this.objectsById.get(state.id);
|
|
1276
|
-
if (!object) {
|
|
1357
|
+
if (!object || (event.ownerId !== undefined && object.ownerId !== event.ownerId)) {
|
|
1277
1358
|
continue;
|
|
1278
1359
|
}
|
|
1279
1360
|
object.applyState(state.state, 'state', state.timestamp, { interestId: response.interestId });
|
|
@@ -1354,7 +1435,8 @@ export class SenBus extends EventEmitter {
|
|
|
1354
1435
|
});
|
|
1355
1436
|
}
|
|
1356
1437
|
|
|
1357
|
-
#requestReadyObjectStates() {
|
|
1438
|
+
#requestReadyObjectStates(options = {}) {
|
|
1439
|
+
const force = options.force === true;
|
|
1358
1440
|
const requestsByInterest = new Map();
|
|
1359
1441
|
for (const interest of this.interests.values()) {
|
|
1360
1442
|
for (const object of interest.objectsById.values()) {
|
|
@@ -1362,7 +1444,7 @@ export class SenBus extends EventEmitter {
|
|
|
1362
1444
|
continue;
|
|
1363
1445
|
}
|
|
1364
1446
|
const key = stateRequestKey(interest.id, object.id);
|
|
1365
|
-
if (this.stateRequestedObjectIds.has(key)) {
|
|
1447
|
+
if (!force && this.stateRequestedObjectIds.has(key)) {
|
|
1366
1448
|
continue;
|
|
1367
1449
|
}
|
|
1368
1450
|
this.stateRequestedObjectIds.add(key);
|
|
@@ -1372,22 +1454,64 @@ export class SenBus extends EventEmitter {
|
|
|
1372
1454
|
}
|
|
1373
1455
|
}
|
|
1374
1456
|
|
|
1375
|
-
if (requestsByInterest.size) {
|
|
1376
|
-
|
|
1377
|
-
interestId,
|
|
1378
|
-
|
|
1379
|
-
|
|
1457
|
+
if (requestsByInterest.size && this.sen.client) {
|
|
1458
|
+
try {
|
|
1459
|
+
this.sen.client.requestObjectStates(this.name, [...requestsByInterest].map(([interestId, objectIds]) => ({
|
|
1460
|
+
interestId,
|
|
1461
|
+
objectIds
|
|
1462
|
+
})));
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
this.sen.emit('warning', error);
|
|
1465
|
+
}
|
|
1380
1466
|
}
|
|
1381
1467
|
}
|
|
1382
1468
|
|
|
1469
|
+
#scheduleStateResyncs() {
|
|
1470
|
+
if (!this.interests.size || this.stateResyncTimers.size) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
for (const delayMs of STATE_RESYNC_DELAYS_MS) {
|
|
1475
|
+
const timer = setTimeout(() => {
|
|
1476
|
+
this.stateResyncTimers.delete(timer);
|
|
1477
|
+
try {
|
|
1478
|
+
this.#requestReadyObjectStates({ force: true });
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
this.sen.emit('warning', error);
|
|
1481
|
+
}
|
|
1482
|
+
}, delayMs);
|
|
1483
|
+
timer.unref?.();
|
|
1484
|
+
this.stateResyncTimers.add(timer);
|
|
1485
|
+
}
|
|
1486
|
+
const interval = setInterval(() => {
|
|
1487
|
+
try {
|
|
1488
|
+
this.#requestReadyObjectStates({ force: true });
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
this.sen.emit('warning', error);
|
|
1491
|
+
}
|
|
1492
|
+
}, STATE_RESYNC_INTERVAL_MS);
|
|
1493
|
+
interval.unref?.();
|
|
1494
|
+
this.stateResyncInterval = interval;
|
|
1495
|
+
this.stateResyncTimers.add(interval);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
#clearStateResyncTimers() {
|
|
1499
|
+
for (const timer of this.stateResyncTimers) {
|
|
1500
|
+
clearTimeout(timer);
|
|
1501
|
+
}
|
|
1502
|
+
this.stateResyncTimers.clear();
|
|
1503
|
+
this.stateResyncInterval = undefined;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1383
1506
|
#retryPendingStates() {
|
|
1384
1507
|
for (const object of this.objectsById.values()) {
|
|
1385
|
-
|
|
1508
|
+
const pendingStates = object.pendingStates.splice(0);
|
|
1509
|
+
for (const pendingState of pendingStates) {
|
|
1386
1510
|
object.applyState(
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
{ interestId:
|
|
1511
|
+
pendingState.buffer,
|
|
1512
|
+
pendingState.source,
|
|
1513
|
+
pendingState.timestampNs,
|
|
1514
|
+
{ interestId: pendingState.interestId }
|
|
1391
1515
|
);
|
|
1392
1516
|
}
|
|
1393
1517
|
}
|
|
@@ -1400,6 +1524,7 @@ export class SenInterest extends EventEmitter {
|
|
|
1400
1524
|
this.bus = bus;
|
|
1401
1525
|
this.id = id;
|
|
1402
1526
|
this.query = query;
|
|
1527
|
+
this.ownerId = undefined;
|
|
1403
1528
|
this.options = { ...options };
|
|
1404
1529
|
this.propertyNames = normalizePropertyNames(options.properties ?? options.propertyNames);
|
|
1405
1530
|
this.changeMode = options.changeMode ?? (options.batch ? 'batch' : 'individual');
|
|
@@ -1491,7 +1616,12 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1491
1616
|
}
|
|
1492
1617
|
this.snapshot = {};
|
|
1493
1618
|
this.spec = undefined;
|
|
1619
|
+
this.typePromise = undefined;
|
|
1494
1620
|
this.pendingState = undefined;
|
|
1621
|
+
this.pendingStates = [];
|
|
1622
|
+
this.readyInterestIds = new Set();
|
|
1623
|
+
this.emittedInterestObjectIds = new Set();
|
|
1624
|
+
this.emittedGlobalObject = false;
|
|
1495
1625
|
this.timestamp = undefined;
|
|
1496
1626
|
this.timestampNs = undefined;
|
|
1497
1627
|
this.lastStateTimestamp = undefined;
|
|
@@ -1520,7 +1650,10 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1520
1650
|
|
|
1521
1651
|
detachInterest(interestId) {
|
|
1522
1652
|
if (interestId !== undefined) {
|
|
1523
|
-
|
|
1653
|
+
const normalizedInterestId = interestId >>> 0;
|
|
1654
|
+
this.interestIds.delete(normalizedInterestId);
|
|
1655
|
+
this.readyInterestIds.delete(normalizedInterestId);
|
|
1656
|
+
this.emittedInterestObjectIds.delete(normalizedInterestId);
|
|
1524
1657
|
if (this.interestId === interestId) {
|
|
1525
1658
|
this.interestId = this.interestIds.values().next().value;
|
|
1526
1659
|
}
|
|
@@ -1552,18 +1685,38 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1552
1685
|
}
|
|
1553
1686
|
|
|
1554
1687
|
const timeoutMs = options.timeout ?? 3000;
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1688
|
+
let timeout;
|
|
1689
|
+
try {
|
|
1690
|
+
return await Promise.race([
|
|
1691
|
+
this.#waitForTypeReady(),
|
|
1692
|
+
new Promise((_, reject) => {
|
|
1693
|
+
timeout = setTimeout(() => {
|
|
1694
|
+
reject(new Error(`timeout waiting for SEN type ${this.className}`));
|
|
1695
|
+
}, timeoutMs);
|
|
1696
|
+
timeout.unref?.();
|
|
1697
|
+
})
|
|
1698
|
+
]);
|
|
1699
|
+
} finally {
|
|
1700
|
+
clearTimeout(timeout);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
#waitForTypeReady() {
|
|
1705
|
+
if (this.spec) {
|
|
1706
|
+
return Promise.resolve(this.spec);
|
|
1707
|
+
}
|
|
1708
|
+
if (this.typePromise) {
|
|
1709
|
+
return this.typePromise;
|
|
1710
|
+
}
|
|
1711
|
+
this.typePromise = new Promise(resolve => {
|
|
1560
1712
|
const onType = spec => {
|
|
1561
|
-
clearTimeout(timeout);
|
|
1562
1713
|
this.off('type', onType);
|
|
1714
|
+
this.typePromise = undefined;
|
|
1563
1715
|
resolve(spec);
|
|
1564
1716
|
};
|
|
1565
1717
|
this.on('type', onType);
|
|
1566
1718
|
});
|
|
1719
|
+
return this.typePromise;
|
|
1567
1720
|
}
|
|
1568
1721
|
|
|
1569
1722
|
async get(name) {
|
|
@@ -1574,6 +1727,27 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1574
1727
|
return this.propertyTimestamps.get(name);
|
|
1575
1728
|
}
|
|
1576
1729
|
|
|
1730
|
+
isReadyForInterest(interestId) {
|
|
1731
|
+
return this.readyInterestIds.has(interestId >>> 0);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
markInterestObjectEmitted(interestId) {
|
|
1735
|
+
const normalizedInterestId = interestId >>> 0;
|
|
1736
|
+
if (this.emittedInterestObjectIds.has(normalizedInterestId)) {
|
|
1737
|
+
return false;
|
|
1738
|
+
}
|
|
1739
|
+
this.emittedInterestObjectIds.add(normalizedInterestId);
|
|
1740
|
+
return true;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
markGlobalObjectEmitted() {
|
|
1744
|
+
if (this.emittedGlobalObject) {
|
|
1745
|
+
return false;
|
|
1746
|
+
}
|
|
1747
|
+
this.emittedGlobalObject = true;
|
|
1748
|
+
return true;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1577
1751
|
async set(name, value, options = {}) {
|
|
1578
1752
|
await this.waitForType(options);
|
|
1579
1753
|
const property = this.property(name);
|
|
@@ -1612,7 +1786,7 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1612
1786
|
this.#rememberObjectTimestamp(source, timestampNs);
|
|
1613
1787
|
|
|
1614
1788
|
if (!this.spec) {
|
|
1615
|
-
this
|
|
1789
|
+
this.#queuePendingState({ buffer, source, timestampNs, interestId: options.interestId });
|
|
1616
1790
|
return;
|
|
1617
1791
|
}
|
|
1618
1792
|
|
|
@@ -1652,7 +1826,32 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1652
1826
|
}
|
|
1653
1827
|
}
|
|
1654
1828
|
|
|
1655
|
-
|
|
1829
|
+
if (complete) {
|
|
1830
|
+
if (source === 'state') {
|
|
1831
|
+
this.#markReady(options.interestId);
|
|
1832
|
+
}
|
|
1833
|
+
if (!this.pendingStates.length) this.pendingState = undefined;
|
|
1834
|
+
} else {
|
|
1835
|
+
this.#queuePendingState({ buffer, source, timestampNs, interestId: options.interestId });
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
#markReady(interestId) {
|
|
1840
|
+
if (interestId !== undefined) {
|
|
1841
|
+
const normalizedInterestId = interestId >>> 0;
|
|
1842
|
+
this.readyInterestIds.add(normalizedInterestId);
|
|
1843
|
+
this.emit('ready', { interestId: normalizedInterestId });
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
for (const id of this.interestIds) {
|
|
1847
|
+
this.readyInterestIds.add(id >>> 0);
|
|
1848
|
+
this.emit('ready', { interestId: id >>> 0 });
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
#queuePendingState(state) {
|
|
1853
|
+
this.pendingStates.push(state);
|
|
1854
|
+
this.pendingState = state;
|
|
1656
1855
|
}
|
|
1657
1856
|
|
|
1658
1857
|
#targetInterests(interestId) {
|