sen-ether-client 0.1.3 → 0.1.5
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 +41 -17
- package/lib/sen.js +251 -55
- 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) => {
|
|
@@ -435,6 +454,15 @@ export class EtherClient extends EventEmitter {
|
|
|
435
454
|
return { busName: busState.busName, busId: busState.busId, id, query };
|
|
436
455
|
}
|
|
437
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
|
+
|
|
438
466
|
/**
|
|
439
467
|
* Stop a previously started interest.
|
|
440
468
|
*
|
|
@@ -711,25 +739,13 @@ export class EtherClient extends EventEmitter {
|
|
|
711
739
|
|
|
712
740
|
#onMulticastBusDatagram(busState, message, remote) {
|
|
713
741
|
try {
|
|
714
|
-
if (message.length <
|
|
742
|
+
if (message.length < 9) {
|
|
715
743
|
throw new RangeError(`SEN multicast bus datagram too small: ${message.length}`);
|
|
716
744
|
}
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
if (processId === this.processInfo.processId) {
|
|
745
|
+
const frame = decodeConfirmedBusFrame(message);
|
|
746
|
+
if (frame.to !== busState.participantId) {
|
|
720
747
|
return;
|
|
721
748
|
}
|
|
722
|
-
if (payloadSize !== message.length - 8) {
|
|
723
|
-
throw new RangeError(
|
|
724
|
-
`SEN multicast bus payload size mismatch: expected ${payloadSize}, got ${message.length - 8}`
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
const frame = {
|
|
729
|
-
to: busState.participantId,
|
|
730
|
-
busId: busState.busId,
|
|
731
|
-
message: message.subarray(8)
|
|
732
|
-
};
|
|
733
749
|
const busMessage = decodeBusMessage(frame.message);
|
|
734
750
|
this.emit('busFrame', { ...frame, busMessage, remote, multicast: true });
|
|
735
751
|
if (busMessage.categoryName === 'controlMessage') {
|
|
@@ -766,7 +782,14 @@ export class EtherClient extends EventEmitter {
|
|
|
766
782
|
const onListening = () => {
|
|
767
783
|
socket.off('error', onError);
|
|
768
784
|
try {
|
|
769
|
-
|
|
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
|
+
}
|
|
770
793
|
socket.setMulticastLoopback(true);
|
|
771
794
|
if (this.interfaceAddress) {
|
|
772
795
|
socket.setMulticastInterface(this.interfaceAddress);
|
|
@@ -789,13 +812,14 @@ export class EtherClient extends EventEmitter {
|
|
|
789
812
|
return;
|
|
790
813
|
}
|
|
791
814
|
|
|
792
|
-
const remoteParticipantId = frame.to;
|
|
815
|
+
const remoteParticipantId = frame.to >>> 0;
|
|
793
816
|
if (!busState.readyRemoteParticipants.has(remoteParticipantId)) {
|
|
794
817
|
busState.readyRemoteParticipants.add(remoteParticipantId);
|
|
795
818
|
this.#sendBusControl(busState, busState.participantId, {
|
|
796
819
|
type: 'RemoteParticipantReady',
|
|
797
820
|
value: { id: remoteParticipantId }
|
|
798
821
|
});
|
|
822
|
+
this.#restartInterestForRemote(busState);
|
|
799
823
|
this.emit('busParticipantReady', {
|
|
800
824
|
busName: busState.busName,
|
|
801
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) => {
|
|
@@ -156,17 +159,14 @@ function stateRequestKey(interestId, objectId) {
|
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
async function waitForSessionBuses(session, timeoutMs) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return buses;
|
|
162
|
+
if (timeoutMs <= 0) {
|
|
163
|
+
return session.listBuses();
|
|
162
164
|
}
|
|
163
|
-
|
|
164
165
|
const deadline = Date.now() + timeoutMs;
|
|
165
|
-
while (
|
|
166
|
+
while (Date.now() < deadline) {
|
|
166
167
|
await wait(Math.min(50, Math.max(1, deadline - Date.now())));
|
|
167
|
-
buses = session.listBuses();
|
|
168
168
|
}
|
|
169
|
-
return
|
|
169
|
+
return session.listBuses();
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
class ChangeBatcher {
|
|
@@ -782,6 +782,13 @@ export class Sen extends EventEmitter {
|
|
|
782
782
|
return target;
|
|
783
783
|
}
|
|
784
784
|
|
|
785
|
+
async #reconnectTarget(options) {
|
|
786
|
+
if (options.tcpHub || !options.target) {
|
|
787
|
+
return await this.#discoverTarget(options);
|
|
788
|
+
}
|
|
789
|
+
return options.target;
|
|
790
|
+
}
|
|
791
|
+
|
|
785
792
|
#sessionNameForBus(busName, options = {}) {
|
|
786
793
|
const explicit = String(options.session || '').trim();
|
|
787
794
|
if (explicit) {
|
|
@@ -899,7 +906,7 @@ export class Sen extends EventEmitter {
|
|
|
899
906
|
}
|
|
900
907
|
|
|
901
908
|
const config = this.connectOptions;
|
|
902
|
-
const target =
|
|
909
|
+
const target = await this.#reconnectTarget(config);
|
|
903
910
|
if (!target) {
|
|
904
911
|
throw new Error('no SEN ether process matches the requested filters');
|
|
905
912
|
}
|
|
@@ -1062,6 +1069,8 @@ export class SenBus extends EventEmitter {
|
|
|
1062
1069
|
this.typeRegistry = new Map();
|
|
1063
1070
|
this.requestedTypeHashes = new Set();
|
|
1064
1071
|
this.stateRequestedObjectIds = new Set();
|
|
1072
|
+
this.stateResyncTimers = new Set();
|
|
1073
|
+
this.stateResyncInterval = undefined;
|
|
1065
1074
|
this.interests = new Map();
|
|
1066
1075
|
this.pendingCalls = new Map();
|
|
1067
1076
|
this.nextTicketId = 1;
|
|
@@ -1076,10 +1085,13 @@ export class SenBus extends EventEmitter {
|
|
|
1076
1085
|
|
|
1077
1086
|
stopInterest(id) {
|
|
1078
1087
|
const interestId = typeof id === 'object' ? id.id : id;
|
|
1079
|
-
this.sen.client
|
|
1088
|
+
this.sen.client?.stopInterest(this.name, interestId);
|
|
1080
1089
|
const interest = this.interests.get(interestId);
|
|
1081
1090
|
this.#detachInterestObjects(interestId, interest);
|
|
1082
1091
|
this.interests.delete(interestId);
|
|
1092
|
+
if (!this.interests.size) {
|
|
1093
|
+
this.#clearStateResyncTimers();
|
|
1094
|
+
}
|
|
1083
1095
|
interest?.closeLocal();
|
|
1084
1096
|
interest?.emit('close');
|
|
1085
1097
|
}
|
|
@@ -1097,7 +1109,7 @@ export class SenBus extends EventEmitter {
|
|
|
1097
1109
|
}
|
|
1098
1110
|
}
|
|
1099
1111
|
try {
|
|
1100
|
-
this.sen.client
|
|
1112
|
+
this.sen.client?.leaveBus(this.name);
|
|
1101
1113
|
} catch (error) {
|
|
1102
1114
|
this.sen.emit('warning', error);
|
|
1103
1115
|
}
|
|
@@ -1117,6 +1129,7 @@ export class SenBus extends EventEmitter {
|
|
|
1117
1129
|
this.typeRegistry.clear();
|
|
1118
1130
|
this.requestedTypeHashes.clear();
|
|
1119
1131
|
this.stateRequestedObjectIds.clear();
|
|
1132
|
+
this.#clearStateResyncTimers();
|
|
1120
1133
|
for (const interest of this.interests.values()) {
|
|
1121
1134
|
interest.closeLocal();
|
|
1122
1135
|
interest.objectsById.clear();
|
|
@@ -1158,8 +1171,25 @@ export class SenBus extends EventEmitter {
|
|
|
1158
1171
|
handleObjectsPublished(event) {
|
|
1159
1172
|
const newTypeHashes = new Set();
|
|
1160
1173
|
for (const discovery of event.discoveries ?? []) {
|
|
1174
|
+
const interest = this.interests.get(discovery.interestId);
|
|
1175
|
+
if (interest && event.ownerId !== undefined) {
|
|
1176
|
+
if (interest.ownerId !== undefined && interest.ownerId !== event.ownerId) {
|
|
1177
|
+
this.#resetInterestForOwner(interest, event.ownerId);
|
|
1178
|
+
} else {
|
|
1179
|
+
interest.ownerId = event.ownerId;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1161
1183
|
for (const info of discovery.objects ?? []) {
|
|
1162
1184
|
let object = this.objectsById.get(info.id);
|
|
1185
|
+
if (object && event.ownerId !== undefined && object.ownerId !== event.ownerId) {
|
|
1186
|
+
this.#removeObjectFromAllInterests(object, {
|
|
1187
|
+
reason: 'ownerChanged',
|
|
1188
|
+
ownerId: event.ownerId,
|
|
1189
|
+
previousOwnerId: object.ownerId
|
|
1190
|
+
});
|
|
1191
|
+
object = undefined;
|
|
1192
|
+
}
|
|
1163
1193
|
const isNewObject = !object;
|
|
1164
1194
|
if (!object) {
|
|
1165
1195
|
object = new SenRemoteObject(this, {
|
|
@@ -1175,26 +1205,22 @@ export class SenBus extends EventEmitter {
|
|
|
1175
1205
|
ownerId: event.ownerId
|
|
1176
1206
|
});
|
|
1177
1207
|
}
|
|
1178
|
-
const interest = this.interests.get(discovery.interestId);
|
|
1179
1208
|
interest?.objectsById.set(object.id, object);
|
|
1180
1209
|
if (info.state?.length) {
|
|
1181
|
-
object.applyState(info.state, '
|
|
1210
|
+
object.applyState(info.state, 'published', info.time, { interestId: discovery.interestId });
|
|
1182
1211
|
}
|
|
1183
1212
|
if (!this.requestedTypeHashes.has(info.typeHash)) {
|
|
1184
1213
|
this.requestedTypeHashes.add(info.typeHash);
|
|
1185
1214
|
newTypeHashes.add(info.typeHash);
|
|
1186
1215
|
}
|
|
1187
|
-
interest
|
|
1188
|
-
if (isNewObject) {
|
|
1189
|
-
this.emit('object', object);
|
|
1190
|
-
this.sen.emit('object', object);
|
|
1191
|
-
}
|
|
1216
|
+
this.#emitObjectWhenReady(interest, object, isNewObject);
|
|
1192
1217
|
}
|
|
1193
1218
|
}
|
|
1194
1219
|
if (newTypeHashes.size) {
|
|
1195
1220
|
this.sen.client.requestTypes(this.name, newTypeHashes);
|
|
1196
1221
|
}
|
|
1197
1222
|
this.#requestReadyObjectStates();
|
|
1223
|
+
this.#scheduleStateResyncs();
|
|
1198
1224
|
}
|
|
1199
1225
|
|
|
1200
1226
|
handleObjectsRemoved(event) {
|
|
@@ -1202,18 +1228,8 @@ export class SenBus extends EventEmitter {
|
|
|
1202
1228
|
for (const id of removal.ids ?? []) {
|
|
1203
1229
|
const object = this.objectsById.get(id);
|
|
1204
1230
|
const interest = this.interests.get(removal.interestId);
|
|
1205
|
-
interest?.objectsById.delete(id);
|
|
1206
|
-
this.stateRequestedObjectIds.delete(stateRequestKey(removal.interestId, id));
|
|
1207
1231
|
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
|
-
}
|
|
1232
|
+
this.#removeObjectFromInterest(object, removal.interestId, interest);
|
|
1217
1233
|
}
|
|
1218
1234
|
}
|
|
1219
1235
|
}
|
|
@@ -1233,13 +1249,74 @@ export class SenBus extends EventEmitter {
|
|
|
1233
1249
|
}
|
|
1234
1250
|
|
|
1235
1251
|
for (const object of interest.objectsById.values()) {
|
|
1236
|
-
object
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1252
|
+
this.#removeObjectFromInterest(object, normalizedInterestId, interest);
|
|
1253
|
+
}
|
|
1254
|
+
interest.objectsById.clear();
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
#resetInterestForOwner(interest, ownerId) {
|
|
1258
|
+
const previousOwnerId = interest.ownerId;
|
|
1259
|
+
const detail = { reason: 'ownerChanged', ownerId, previousOwnerId };
|
|
1260
|
+
for (const object of [...interest.objectsById.values()]) {
|
|
1261
|
+
this.#removeObjectFromAllInterests(object, detail);
|
|
1241
1262
|
}
|
|
1242
1263
|
interest.objectsById.clear();
|
|
1264
|
+
interest.ownerId = ownerId;
|
|
1265
|
+
interest.resetLocal();
|
|
1266
|
+
interest.emit('stale', detail);
|
|
1267
|
+
this.emit('stale', { interest, ...detail });
|
|
1268
|
+
this.sen.emit('stale', { bus: this, interest, ...detail });
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
#removeObjectFromAllInterests(object, detail = {}) {
|
|
1272
|
+
for (const interestId of [...object.interestIds]) {
|
|
1273
|
+
this.#removeObjectFromInterest(object, interestId, this.interests.get(interestId), detail);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
#removeObjectFromInterest(object, interestId, interest, detail = {}) {
|
|
1278
|
+
const normalizedInterestId = interestId >>> 0;
|
|
1279
|
+
this.stateRequestedObjectIds.delete(stateRequestKey(normalizedInterestId, object.id));
|
|
1280
|
+
interest?.objectsById.delete(object.id);
|
|
1281
|
+
object.detachInterest(normalizedInterestId);
|
|
1282
|
+
object.emit('remove', { interestId: normalizedInterestId, ...detail });
|
|
1283
|
+
interest?.emit('remove', object);
|
|
1284
|
+
if (object.interestIds.size === 0) {
|
|
1285
|
+
this.objectsById.delete(object.id);
|
|
1286
|
+
this.requestedTypeHashes.delete(object.typeHash);
|
|
1287
|
+
this.emit('remove', object);
|
|
1288
|
+
this.sen.emit('remove', object);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
#emitObjectWhenReady(interest, object, emitGlobal) {
|
|
1293
|
+
if (!interest) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const publish = () => {
|
|
1297
|
+
if (!object.isReadyForInterest(interest.id)) {
|
|
1298
|
+
return false;
|
|
1299
|
+
}
|
|
1300
|
+
if (object.markInterestObjectEmitted(interest.id)) {
|
|
1301
|
+
interest.emit('object', object);
|
|
1302
|
+
}
|
|
1303
|
+
if (emitGlobal && object.markGlobalObjectEmitted()) {
|
|
1304
|
+
this.emit('object', object);
|
|
1305
|
+
this.sen.emit('object', object);
|
|
1306
|
+
}
|
|
1307
|
+
return true;
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
if (publish()) {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
const onReady = () => {
|
|
1315
|
+
if (publish()) {
|
|
1316
|
+
object.off('ready', onReady);
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
object.on('ready', onReady);
|
|
1243
1320
|
}
|
|
1244
1321
|
|
|
1245
1322
|
handleTypesInfoResponse(event) {
|
|
@@ -1267,13 +1344,14 @@ export class SenBus extends EventEmitter {
|
|
|
1267
1344
|
}
|
|
1268
1345
|
this.#retryPendingStates();
|
|
1269
1346
|
this.#requestReadyObjectStates();
|
|
1347
|
+
this.#scheduleStateResyncs();
|
|
1270
1348
|
}
|
|
1271
1349
|
|
|
1272
1350
|
handleObjectsStateResponse(event) {
|
|
1273
1351
|
for (const response of event.responses ?? []) {
|
|
1274
1352
|
for (const state of response.objectStates ?? []) {
|
|
1275
1353
|
const object = this.objectsById.get(state.id);
|
|
1276
|
-
if (!object) {
|
|
1354
|
+
if (!object || (event.ownerId !== undefined && object.ownerId !== event.ownerId)) {
|
|
1277
1355
|
continue;
|
|
1278
1356
|
}
|
|
1279
1357
|
object.applyState(state.state, 'state', state.timestamp, { interestId: response.interestId });
|
|
@@ -1354,7 +1432,8 @@ export class SenBus extends EventEmitter {
|
|
|
1354
1432
|
});
|
|
1355
1433
|
}
|
|
1356
1434
|
|
|
1357
|
-
#requestReadyObjectStates() {
|
|
1435
|
+
#requestReadyObjectStates(options = {}) {
|
|
1436
|
+
const force = options.force === true;
|
|
1358
1437
|
const requestsByInterest = new Map();
|
|
1359
1438
|
for (const interest of this.interests.values()) {
|
|
1360
1439
|
for (const object of interest.objectsById.values()) {
|
|
@@ -1362,7 +1441,7 @@ export class SenBus extends EventEmitter {
|
|
|
1362
1441
|
continue;
|
|
1363
1442
|
}
|
|
1364
1443
|
const key = stateRequestKey(interest.id, object.id);
|
|
1365
|
-
if (this.stateRequestedObjectIds.has(key)) {
|
|
1444
|
+
if (!force && this.stateRequestedObjectIds.has(key)) {
|
|
1366
1445
|
continue;
|
|
1367
1446
|
}
|
|
1368
1447
|
this.stateRequestedObjectIds.add(key);
|
|
@@ -1372,22 +1451,64 @@ export class SenBus extends EventEmitter {
|
|
|
1372
1451
|
}
|
|
1373
1452
|
}
|
|
1374
1453
|
|
|
1375
|
-
if (requestsByInterest.size) {
|
|
1376
|
-
|
|
1377
|
-
interestId,
|
|
1378
|
-
|
|
1379
|
-
|
|
1454
|
+
if (requestsByInterest.size && this.sen.client) {
|
|
1455
|
+
try {
|
|
1456
|
+
this.sen.client.requestObjectStates(this.name, [...requestsByInterest].map(([interestId, objectIds]) => ({
|
|
1457
|
+
interestId,
|
|
1458
|
+
objectIds
|
|
1459
|
+
})));
|
|
1460
|
+
} catch (error) {
|
|
1461
|
+
this.sen.emit('warning', error);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
#scheduleStateResyncs() {
|
|
1467
|
+
if (!this.interests.size || this.stateResyncTimers.size) {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
for (const delayMs of STATE_RESYNC_DELAYS_MS) {
|
|
1472
|
+
const timer = setTimeout(() => {
|
|
1473
|
+
this.stateResyncTimers.delete(timer);
|
|
1474
|
+
try {
|
|
1475
|
+
this.#requestReadyObjectStates({ force: true });
|
|
1476
|
+
} catch (error) {
|
|
1477
|
+
this.sen.emit('warning', error);
|
|
1478
|
+
}
|
|
1479
|
+
}, delayMs);
|
|
1480
|
+
timer.unref?.();
|
|
1481
|
+
this.stateResyncTimers.add(timer);
|
|
1482
|
+
}
|
|
1483
|
+
const interval = setInterval(() => {
|
|
1484
|
+
try {
|
|
1485
|
+
this.#requestReadyObjectStates({ force: true });
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
this.sen.emit('warning', error);
|
|
1488
|
+
}
|
|
1489
|
+
}, STATE_RESYNC_INTERVAL_MS);
|
|
1490
|
+
interval.unref?.();
|
|
1491
|
+
this.stateResyncInterval = interval;
|
|
1492
|
+
this.stateResyncTimers.add(interval);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
#clearStateResyncTimers() {
|
|
1496
|
+
for (const timer of this.stateResyncTimers) {
|
|
1497
|
+
clearTimeout(timer);
|
|
1380
1498
|
}
|
|
1499
|
+
this.stateResyncTimers.clear();
|
|
1500
|
+
this.stateResyncInterval = undefined;
|
|
1381
1501
|
}
|
|
1382
1502
|
|
|
1383
1503
|
#retryPendingStates() {
|
|
1384
1504
|
for (const object of this.objectsById.values()) {
|
|
1385
|
-
|
|
1505
|
+
const pendingStates = object.pendingStates.splice(0);
|
|
1506
|
+
for (const pendingState of pendingStates) {
|
|
1386
1507
|
object.applyState(
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
{ interestId:
|
|
1508
|
+
pendingState.buffer,
|
|
1509
|
+
pendingState.source,
|
|
1510
|
+
pendingState.timestampNs,
|
|
1511
|
+
{ interestId: pendingState.interestId }
|
|
1391
1512
|
);
|
|
1392
1513
|
}
|
|
1393
1514
|
}
|
|
@@ -1400,6 +1521,7 @@ export class SenInterest extends EventEmitter {
|
|
|
1400
1521
|
this.bus = bus;
|
|
1401
1522
|
this.id = id;
|
|
1402
1523
|
this.query = query;
|
|
1524
|
+
this.ownerId = undefined;
|
|
1403
1525
|
this.options = { ...options };
|
|
1404
1526
|
this.propertyNames = normalizePropertyNames(options.properties ?? options.propertyNames);
|
|
1405
1527
|
this.changeMode = options.changeMode ?? (options.batch ? 'batch' : 'individual');
|
|
@@ -1491,7 +1613,12 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1491
1613
|
}
|
|
1492
1614
|
this.snapshot = {};
|
|
1493
1615
|
this.spec = undefined;
|
|
1616
|
+
this.typePromise = undefined;
|
|
1494
1617
|
this.pendingState = undefined;
|
|
1618
|
+
this.pendingStates = [];
|
|
1619
|
+
this.readyInterestIds = new Set();
|
|
1620
|
+
this.emittedInterestObjectIds = new Set();
|
|
1621
|
+
this.emittedGlobalObject = false;
|
|
1495
1622
|
this.timestamp = undefined;
|
|
1496
1623
|
this.timestampNs = undefined;
|
|
1497
1624
|
this.lastStateTimestamp = undefined;
|
|
@@ -1520,7 +1647,10 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1520
1647
|
|
|
1521
1648
|
detachInterest(interestId) {
|
|
1522
1649
|
if (interestId !== undefined) {
|
|
1523
|
-
|
|
1650
|
+
const normalizedInterestId = interestId >>> 0;
|
|
1651
|
+
this.interestIds.delete(normalizedInterestId);
|
|
1652
|
+
this.readyInterestIds.delete(normalizedInterestId);
|
|
1653
|
+
this.emittedInterestObjectIds.delete(normalizedInterestId);
|
|
1524
1654
|
if (this.interestId === interestId) {
|
|
1525
1655
|
this.interestId = this.interestIds.values().next().value;
|
|
1526
1656
|
}
|
|
@@ -1552,18 +1682,38 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1552
1682
|
}
|
|
1553
1683
|
|
|
1554
1684
|
const timeoutMs = options.timeout ?? 3000;
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1685
|
+
let timeout;
|
|
1686
|
+
try {
|
|
1687
|
+
return await Promise.race([
|
|
1688
|
+
this.#waitForTypeReady(),
|
|
1689
|
+
new Promise((_, reject) => {
|
|
1690
|
+
timeout = setTimeout(() => {
|
|
1691
|
+
reject(new Error(`timeout waiting for SEN type ${this.className}`));
|
|
1692
|
+
}, timeoutMs);
|
|
1693
|
+
timeout.unref?.();
|
|
1694
|
+
})
|
|
1695
|
+
]);
|
|
1696
|
+
} finally {
|
|
1697
|
+
clearTimeout(timeout);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
#waitForTypeReady() {
|
|
1702
|
+
if (this.spec) {
|
|
1703
|
+
return Promise.resolve(this.spec);
|
|
1704
|
+
}
|
|
1705
|
+
if (this.typePromise) {
|
|
1706
|
+
return this.typePromise;
|
|
1707
|
+
}
|
|
1708
|
+
this.typePromise = new Promise(resolve => {
|
|
1560
1709
|
const onType = spec => {
|
|
1561
|
-
clearTimeout(timeout);
|
|
1562
1710
|
this.off('type', onType);
|
|
1711
|
+
this.typePromise = undefined;
|
|
1563
1712
|
resolve(spec);
|
|
1564
1713
|
};
|
|
1565
1714
|
this.on('type', onType);
|
|
1566
1715
|
});
|
|
1716
|
+
return this.typePromise;
|
|
1567
1717
|
}
|
|
1568
1718
|
|
|
1569
1719
|
async get(name) {
|
|
@@ -1574,6 +1724,27 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1574
1724
|
return this.propertyTimestamps.get(name);
|
|
1575
1725
|
}
|
|
1576
1726
|
|
|
1727
|
+
isReadyForInterest(interestId) {
|
|
1728
|
+
return this.readyInterestIds.has(interestId >>> 0);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
markInterestObjectEmitted(interestId) {
|
|
1732
|
+
const normalizedInterestId = interestId >>> 0;
|
|
1733
|
+
if (this.emittedInterestObjectIds.has(normalizedInterestId)) {
|
|
1734
|
+
return false;
|
|
1735
|
+
}
|
|
1736
|
+
this.emittedInterestObjectIds.add(normalizedInterestId);
|
|
1737
|
+
return true;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
markGlobalObjectEmitted() {
|
|
1741
|
+
if (this.emittedGlobalObject) {
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
this.emittedGlobalObject = true;
|
|
1745
|
+
return true;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1577
1748
|
async set(name, value, options = {}) {
|
|
1578
1749
|
await this.waitForType(options);
|
|
1579
1750
|
const property = this.property(name);
|
|
@@ -1612,7 +1783,7 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1612
1783
|
this.#rememberObjectTimestamp(source, timestampNs);
|
|
1613
1784
|
|
|
1614
1785
|
if (!this.spec) {
|
|
1615
|
-
this
|
|
1786
|
+
this.#queuePendingState({ buffer, source, timestampNs, interestId: options.interestId });
|
|
1616
1787
|
return;
|
|
1617
1788
|
}
|
|
1618
1789
|
|
|
@@ -1652,7 +1823,32 @@ export class SenRemoteObject extends EventEmitter {
|
|
|
1652
1823
|
}
|
|
1653
1824
|
}
|
|
1654
1825
|
|
|
1655
|
-
|
|
1826
|
+
if (complete) {
|
|
1827
|
+
if (source === 'state') {
|
|
1828
|
+
this.#markReady(options.interestId);
|
|
1829
|
+
}
|
|
1830
|
+
if (!this.pendingStates.length) this.pendingState = undefined;
|
|
1831
|
+
} else {
|
|
1832
|
+
this.#queuePendingState({ buffer, source, timestampNs, interestId: options.interestId });
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
#markReady(interestId) {
|
|
1837
|
+
if (interestId !== undefined) {
|
|
1838
|
+
const normalizedInterestId = interestId >>> 0;
|
|
1839
|
+
this.readyInterestIds.add(normalizedInterestId);
|
|
1840
|
+
this.emit('ready', { interestId: normalizedInterestId });
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
for (const id of this.interestIds) {
|
|
1844
|
+
this.readyInterestIds.add(id >>> 0);
|
|
1845
|
+
this.emit('ready', { interestId: id >>> 0 });
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
#queuePendingState(state) {
|
|
1850
|
+
this.pendingStates.push(state);
|
|
1851
|
+
this.pendingState = state;
|
|
1656
1852
|
}
|
|
1657
1853
|
|
|
1658
1854
|
#targetInterests(interestId) {
|