@hla4ts/spacekit 0.1.0
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/README.md +154 -0
- package/package.json +28 -0
- package/src/app-export.test.ts +45 -0
- package/src/app.ts +1018 -0
- package/src/declarative-runtime.test.ts +271 -0
- package/src/declarative-runtime.ts +541 -0
- package/src/declarative.test.ts +49 -0
- package/src/declarative.ts +254 -0
- package/src/declare-spacefom.ts +14 -0
- package/src/decorators.test.ts +133 -0
- package/src/decorators.ts +514 -0
- package/src/entity.ts +103 -0
- package/src/env.ts +168 -0
- package/src/federate.ts +205 -0
- package/src/index.ts +51 -0
- package/src/logger.ts +45 -0
- package/src/object-model.ts +275 -0
- package/src/see-app.test.ts +62 -0
- package/src/see-app.ts +460 -0
- package/src/spacefom-bootstrap.test.ts +10 -0
- package/src/spacefom-bootstrap.ts +596 -0
- package/src/spacefom-config.ts +25 -0
- package/src/spacefom-decorators.test.ts +27 -0
- package/src/spacefom-entities.ts +546 -0
- package/src/spacefom-interactions.ts +33 -0
- package/src/time-advance.ts +46 -0
- package/src/types.ts +27 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decodeHLAinteger64Time,
|
|
3
|
+
encodeHLAinteger64Interval,
|
|
4
|
+
encodeHLAinteger64Time,
|
|
5
|
+
handleToHex,
|
|
6
|
+
type AttributeHandleValueMap,
|
|
7
|
+
type LogicalTime,
|
|
8
|
+
type LogicalTimeInterval,
|
|
9
|
+
type ObjectInstanceHandle,
|
|
10
|
+
type RTIAmbassador,
|
|
11
|
+
} from "@hla4ts/hla-api";
|
|
12
|
+
import type { FederateHandlers } from "./types.ts";
|
|
13
|
+
import { buildDeclarativeFomFromDecorators } from "./decorators.ts";
|
|
14
|
+
import type {
|
|
15
|
+
DynamicalEntityAttributes,
|
|
16
|
+
ExecutionConfigurationAttributes,
|
|
17
|
+
PhysicalEntityAttributes,
|
|
18
|
+
ReferenceFrameAttributes,
|
|
19
|
+
} from "./spacefom-entities.ts";
|
|
20
|
+
import { ExecutionConfiguration, ReferenceFrame, PhysicalEntity, DynamicalEntity } from "./spacefom-entities.ts";
|
|
21
|
+
import {
|
|
22
|
+
SpaceFomExecutionMode,
|
|
23
|
+
SpaceFomSyncPointLabels,
|
|
24
|
+
type SpaceTimeCoordinateState,
|
|
25
|
+
} from "@hla4ts/spacefom";
|
|
26
|
+
|
|
27
|
+
export interface SpaceFomLateJoinerOptions {
|
|
28
|
+
syncPoints?: {
|
|
29
|
+
initializationCompleted?: string;
|
|
30
|
+
shutdown?: string;
|
|
31
|
+
mtrRun?: string;
|
|
32
|
+
mtrFreeze?: string;
|
|
33
|
+
};
|
|
34
|
+
waitForInitializationCompleted?: boolean;
|
|
35
|
+
autoCompleteInitialization?: boolean;
|
|
36
|
+
subscribeExecutionConfiguration?: boolean;
|
|
37
|
+
subscribeReferenceFrame?: boolean;
|
|
38
|
+
subscribePhysicalEntity?: boolean;
|
|
39
|
+
subscribeDynamicalEntity?: boolean;
|
|
40
|
+
requestAttributeUpdates?: boolean;
|
|
41
|
+
waitForExco?: boolean;
|
|
42
|
+
waitForReferenceFrameTree?: boolean;
|
|
43
|
+
referenceFrameTimeoutMs?: number;
|
|
44
|
+
enableTimeManagement?: boolean;
|
|
45
|
+
lookaheadMicros?: bigint;
|
|
46
|
+
advanceToHltb?: boolean;
|
|
47
|
+
awaitTimeAdvanceGrant?: boolean;
|
|
48
|
+
onExecutionConfigurationUpdate?: (payload: {
|
|
49
|
+
state: SpaceFomBootstrapState;
|
|
50
|
+
}) => void;
|
|
51
|
+
onExecutionModeChange?: (payload: {
|
|
52
|
+
current: SpaceFomExecutionMode;
|
|
53
|
+
next?: SpaceFomExecutionMode;
|
|
54
|
+
previousCurrent?: SpaceFomExecutionMode;
|
|
55
|
+
previousNext?: SpaceFomExecutionMode;
|
|
56
|
+
state: SpaceFomBootstrapState;
|
|
57
|
+
}) => void;
|
|
58
|
+
onTimeRegulationEnabled?: (payload: {
|
|
59
|
+
timeMicros: bigint;
|
|
60
|
+
state: SpaceFomBootstrapState;
|
|
61
|
+
}) => void;
|
|
62
|
+
onTimeConstrainedEnabled?: (payload: {
|
|
63
|
+
timeMicros: bigint;
|
|
64
|
+
state: SpaceFomBootstrapState;
|
|
65
|
+
}) => void;
|
|
66
|
+
onTimeAdvanceGrant?: (payload: {
|
|
67
|
+
timeMicros: bigint;
|
|
68
|
+
state: SpaceFomBootstrapState;
|
|
69
|
+
}) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type SpaceFomLateJoinerOptionsResolved = Required<
|
|
73
|
+
Omit<
|
|
74
|
+
SpaceFomLateJoinerOptions,
|
|
75
|
+
| "onExecutionConfigurationUpdate"
|
|
76
|
+
| "onExecutionModeChange"
|
|
77
|
+
| "onTimeRegulationEnabled"
|
|
78
|
+
| "onTimeConstrainedEnabled"
|
|
79
|
+
| "onTimeAdvanceGrant"
|
|
80
|
+
>
|
|
81
|
+
> &
|
|
82
|
+
Pick<
|
|
83
|
+
SpaceFomLateJoinerOptions,
|
|
84
|
+
| "onExecutionConfigurationUpdate"
|
|
85
|
+
| "onExecutionModeChange"
|
|
86
|
+
| "onTimeRegulationEnabled"
|
|
87
|
+
| "onTimeConstrainedEnabled"
|
|
88
|
+
| "onTimeAdvanceGrant"
|
|
89
|
+
>;
|
|
90
|
+
|
|
91
|
+
export interface SpaceFomReferenceFrameSnapshot {
|
|
92
|
+
objectInstance: ObjectInstanceHandle;
|
|
93
|
+
name?: string;
|
|
94
|
+
parentName?: string;
|
|
95
|
+
state?: SpaceTimeCoordinateState;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface SpaceFomBootstrapState {
|
|
99
|
+
excoInstance?: ObjectInstanceHandle;
|
|
100
|
+
rootFrameName?: string;
|
|
101
|
+
scenarioTimeEpoch?: number;
|
|
102
|
+
lctsMicros?: bigint;
|
|
103
|
+
currentExecutionMode?: SpaceFomExecutionMode;
|
|
104
|
+
nextExecutionMode?: SpaceFomExecutionMode;
|
|
105
|
+
nextModeScenarioTime?: number;
|
|
106
|
+
nextModeCteTime?: number;
|
|
107
|
+
initializationCompletedAnnounced?: boolean;
|
|
108
|
+
initializationCompletedAuto?: boolean;
|
|
109
|
+
shutdownAnnounced?: boolean;
|
|
110
|
+
mtrRunAnnounced?: boolean;
|
|
111
|
+
mtrFreezeAnnounced?: boolean;
|
|
112
|
+
referenceFrameTreeReady?: boolean;
|
|
113
|
+
lastGrantedTimeMicros?: bigint;
|
|
114
|
+
timeRegulationEnabledTimeMicros?: bigint;
|
|
115
|
+
timeConstrainedEnabledTimeMicros?: bigint;
|
|
116
|
+
referenceFrames?: SpaceFomReferenceFrameSnapshot[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface SpaceFomReferenceFrameRecord extends SpaceFomReferenceFrameSnapshot {
|
|
120
|
+
key: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const SPACEFOM_DECLARATIVE = buildDeclarativeFomFromDecorators({
|
|
124
|
+
objectClasses: [ExecutionConfiguration, ReferenceFrame, PhysicalEntity, DynamicalEntity],
|
|
125
|
+
register: false,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export class SpaceFomLateJoinerBootstrap {
|
|
129
|
+
readonly handlers: FederateHandlers;
|
|
130
|
+
private readonly _options: SpaceFomLateJoinerOptionsResolved;
|
|
131
|
+
private _initCompletedResolve!: () => void;
|
|
132
|
+
private _initCompletedReject!: (err: Error) => void;
|
|
133
|
+
private _timeRegulationResolve!: () => void;
|
|
134
|
+
private _timeConstrainedResolve!: () => void;
|
|
135
|
+
private _timeAdvanceResolve?: () => void;
|
|
136
|
+
private _excoReadyResolve!: () => void;
|
|
137
|
+
private _referenceFrameReadyResolve!: () => void;
|
|
138
|
+
private _shutdownTriggered = false;
|
|
139
|
+
private _excoReady = false;
|
|
140
|
+
private _referenceFrameTreeReady = false;
|
|
141
|
+
private _initCompletedResolved = false;
|
|
142
|
+
private _initCompletedPromise: Promise<void>;
|
|
143
|
+
private _timeRegulationPromise: Promise<void>;
|
|
144
|
+
private _timeConstrainedPromise: Promise<void>;
|
|
145
|
+
private _timeAdvancePromise: Promise<void> | null = null;
|
|
146
|
+
private _excoReadyPromise: Promise<void>;
|
|
147
|
+
private _referenceFrameReadyPromise: Promise<void>;
|
|
148
|
+
private _state: SpaceFomBootstrapState = {};
|
|
149
|
+
private readonly _executionConfiguration = SPACEFOM_DECLARATIVE.getObjectAdapter<ExecutionConfigurationAttributes>(ExecutionConfiguration.className)!;
|
|
150
|
+
private readonly _referenceFrame = SPACEFOM_DECLARATIVE.getObjectAdapter<ReferenceFrameAttributes>(ReferenceFrame.className)!;
|
|
151
|
+
private readonly _physicalEntity = SPACEFOM_DECLARATIVE.getObjectAdapter<PhysicalEntityAttributes>(PhysicalEntity.className)!;
|
|
152
|
+
private readonly _dynamicalEntity = SPACEFOM_DECLARATIVE.getObjectAdapter<DynamicalEntityAttributes>(DynamicalEntity.className)!;
|
|
153
|
+
private readonly _referenceFramesByInstance = new Map<string, SpaceFomReferenceFrameRecord>();
|
|
154
|
+
private readonly _referenceFramesByName = new Map<string, SpaceFomReferenceFrameRecord>();
|
|
155
|
+
|
|
156
|
+
constructor(options: SpaceFomLateJoinerOptions = {}) {
|
|
157
|
+
this._options = {
|
|
158
|
+
syncPoints: {
|
|
159
|
+
initializationCompleted:
|
|
160
|
+
options.syncPoints?.initializationCompleted ??
|
|
161
|
+
SpaceFomSyncPointLabels.initializationCompleted,
|
|
162
|
+
shutdown: options.syncPoints?.shutdown ?? SpaceFomSyncPointLabels.mtrShutdown,
|
|
163
|
+
mtrRun: options.syncPoints?.mtrRun ?? SpaceFomSyncPointLabels.mtrRun,
|
|
164
|
+
mtrFreeze: options.syncPoints?.mtrFreeze ?? SpaceFomSyncPointLabels.mtrFreeze,
|
|
165
|
+
},
|
|
166
|
+
waitForInitializationCompleted: options.waitForInitializationCompleted ?? false,
|
|
167
|
+
autoCompleteInitialization: options.autoCompleteInitialization ?? true,
|
|
168
|
+
subscribeExecutionConfiguration: options.subscribeExecutionConfiguration ?? true,
|
|
169
|
+
subscribeReferenceFrame: options.subscribeReferenceFrame ?? true,
|
|
170
|
+
subscribePhysicalEntity: options.subscribePhysicalEntity ?? true,
|
|
171
|
+
subscribeDynamicalEntity: options.subscribeDynamicalEntity ?? true,
|
|
172
|
+
requestAttributeUpdates: options.requestAttributeUpdates ?? true,
|
|
173
|
+
waitForExco: options.waitForExco ?? true,
|
|
174
|
+
waitForReferenceFrameTree: options.waitForReferenceFrameTree ?? true,
|
|
175
|
+
referenceFrameTimeoutMs: options.referenceFrameTimeoutMs ?? 0,
|
|
176
|
+
enableTimeManagement: options.enableTimeManagement ?? true,
|
|
177
|
+
lookaheadMicros: options.lookaheadMicros ?? 1_000_000n,
|
|
178
|
+
advanceToHltb: options.advanceToHltb ?? true,
|
|
179
|
+
awaitTimeAdvanceGrant: options.awaitTimeAdvanceGrant ?? true,
|
|
180
|
+
onExecutionConfigurationUpdate: options.onExecutionConfigurationUpdate,
|
|
181
|
+
onExecutionModeChange: options.onExecutionModeChange,
|
|
182
|
+
onTimeRegulationEnabled: options.onTimeRegulationEnabled,
|
|
183
|
+
onTimeConstrainedEnabled: options.onTimeConstrainedEnabled,
|
|
184
|
+
onTimeAdvanceGrant: options.onTimeAdvanceGrant,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
this._initCompletedPromise = new Promise((resolve, reject) => {
|
|
188
|
+
this._initCompletedResolve = resolve;
|
|
189
|
+
this._initCompletedReject = reject;
|
|
190
|
+
});
|
|
191
|
+
this._timeRegulationPromise = new Promise((resolve) => {
|
|
192
|
+
this._timeRegulationResolve = resolve;
|
|
193
|
+
});
|
|
194
|
+
this._timeConstrainedPromise = new Promise((resolve) => {
|
|
195
|
+
this._timeConstrainedResolve = resolve;
|
|
196
|
+
});
|
|
197
|
+
this._excoReadyPromise = new Promise((resolve) => {
|
|
198
|
+
this._excoReadyResolve = resolve;
|
|
199
|
+
});
|
|
200
|
+
this._referenceFrameReadyPromise = new Promise((resolve) => {
|
|
201
|
+
this._referenceFrameReadyResolve = resolve;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
this.handlers = {
|
|
205
|
+
announceSynchronizationPoint: (label) => {
|
|
206
|
+
if (label === this._options.syncPoints.shutdown) {
|
|
207
|
+
this._shutdownTriggered = true;
|
|
208
|
+
this._state.shutdownAnnounced = true;
|
|
209
|
+
if (!this._initCompletedResolved) {
|
|
210
|
+
this._initCompletedReject(
|
|
211
|
+
new Error("Federation shutting down (mtr_shutdown announced).")
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (label === this._options.syncPoints.initializationCompleted) {
|
|
217
|
+
this._state.initializationCompletedAnnounced = true;
|
|
218
|
+
this._resolveInitializationCompleted();
|
|
219
|
+
}
|
|
220
|
+
if (label === this._options.syncPoints.mtrRun) {
|
|
221
|
+
this._state.mtrRunAnnounced = true;
|
|
222
|
+
}
|
|
223
|
+
if (label === this._options.syncPoints.mtrFreeze) {
|
|
224
|
+
this._state.mtrFreezeAnnounced = true;
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
discoverObjectInstance: (objectInstance, objectClass, objectInstanceName) => {
|
|
228
|
+
if (this._executionConfiguration.isResolved) {
|
|
229
|
+
const excoHandle = this._executionConfiguration.classHandle;
|
|
230
|
+
if (handleToHex(objectClass) === handleToHex(excoHandle)) {
|
|
231
|
+
this._state.excoInstance = objectInstance;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (this._referenceFrame.isResolved) {
|
|
236
|
+
const frameHandle = this._referenceFrame.classHandle;
|
|
237
|
+
if (handleToHex(objectClass) === handleToHex(frameHandle)) {
|
|
238
|
+
this._registerReferenceFrame(objectInstance, objectInstanceName);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
reflectAttributeValues: (objectInstance, attributeValues) => {
|
|
243
|
+
this._applyAttributes(objectInstance, attributeValues);
|
|
244
|
+
},
|
|
245
|
+
reflectAttributeValuesWithTime: (objectInstance, attributeValues) => {
|
|
246
|
+
this._applyAttributes(objectInstance, attributeValues);
|
|
247
|
+
},
|
|
248
|
+
timeRegulationEnabled: (time) => {
|
|
249
|
+
this._state.timeRegulationEnabledTimeMicros = decodeHLAinteger64Time(time);
|
|
250
|
+
this._timeRegulationResolve();
|
|
251
|
+
this._options.onTimeRegulationEnabled?.({
|
|
252
|
+
timeMicros: this._state.timeRegulationEnabledTimeMicros,
|
|
253
|
+
state: this.state,
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
timeConstrainedEnabled: (time) => {
|
|
257
|
+
this._state.timeConstrainedEnabledTimeMicros = decodeHLAinteger64Time(time);
|
|
258
|
+
this._timeConstrainedResolve();
|
|
259
|
+
this._options.onTimeConstrainedEnabled?.({
|
|
260
|
+
timeMicros: this._state.timeConstrainedEnabledTimeMicros,
|
|
261
|
+
state: this.state,
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
timeAdvanceGrant: (time) => {
|
|
265
|
+
this._state.lastGrantedTimeMicros = decodeHLAinteger64Time(time);
|
|
266
|
+
if (this._timeAdvanceResolve) {
|
|
267
|
+
this._timeAdvanceResolve();
|
|
268
|
+
this._timeAdvanceResolve = undefined;
|
|
269
|
+
this._timeAdvancePromise = null;
|
|
270
|
+
}
|
|
271
|
+
this._options.onTimeAdvanceGrant?.({
|
|
272
|
+
timeMicros: this._state.lastGrantedTimeMicros,
|
|
273
|
+
state: this.state,
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
get state(): SpaceFomBootstrapState {
|
|
280
|
+
return {
|
|
281
|
+
...this._state,
|
|
282
|
+
referenceFrames: Array.from(this._referenceFramesByInstance.values()).map((frame) => ({
|
|
283
|
+
objectInstance: frame.objectInstance,
|
|
284
|
+
name: frame.name,
|
|
285
|
+
parentName: frame.parentName,
|
|
286
|
+
state: frame.state,
|
|
287
|
+
})),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async run(rti: RTIAmbassador): Promise<SpaceFomBootstrapState> {
|
|
292
|
+
if (this._shutdownTriggered) {
|
|
293
|
+
throw new Error("Shutdown sync point already announced.");
|
|
294
|
+
}
|
|
295
|
+
if (this._options.waitForExco && !this._options.subscribeExecutionConfiguration) {
|
|
296
|
+
throw new Error("waitForExco requires subscribeExecutionConfiguration.");
|
|
297
|
+
}
|
|
298
|
+
if (this._options.waitForReferenceFrameTree && !this._options.subscribeReferenceFrame) {
|
|
299
|
+
throw new Error("waitForReferenceFrameTree requires subscribeReferenceFrame.");
|
|
300
|
+
}
|
|
301
|
+
if (this._options.advanceToHltb && !this._options.enableTimeManagement) {
|
|
302
|
+
throw new Error("advanceToHltb requires enableTimeManagement.");
|
|
303
|
+
}
|
|
304
|
+
if (this._options.advanceToHltb && !this._options.subscribeExecutionConfiguration) {
|
|
305
|
+
throw new Error("advanceToHltb requires subscribeExecutionConfiguration.");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
await this._resolveHandles(rti);
|
|
309
|
+
await this._subscribe(rti);
|
|
310
|
+
if (this._options.requestAttributeUpdates) {
|
|
311
|
+
await this._requestAttributeUpdates(rti);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (this._options.waitForInitializationCompleted) {
|
|
315
|
+
await this._initCompletedPromise;
|
|
316
|
+
this._throwIfShutdown();
|
|
317
|
+
}
|
|
318
|
+
if (this._options.waitForExco) {
|
|
319
|
+
await this._excoReadyPromise;
|
|
320
|
+
this._throwIfShutdown();
|
|
321
|
+
}
|
|
322
|
+
if (this._options.waitForReferenceFrameTree) {
|
|
323
|
+
await this._waitForReferenceFrameTree();
|
|
324
|
+
this._throwIfShutdown();
|
|
325
|
+
}
|
|
326
|
+
if (this._options.enableTimeManagement) {
|
|
327
|
+
await this._enableTimeManagement(rti);
|
|
328
|
+
this._throwIfShutdown();
|
|
329
|
+
}
|
|
330
|
+
if (this._options.advanceToHltb) {
|
|
331
|
+
await this._advanceToHltb(rti);
|
|
332
|
+
this._throwIfShutdown();
|
|
333
|
+
}
|
|
334
|
+
return this.state;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private _throwIfShutdown(): void {
|
|
338
|
+
if (this._shutdownTriggered) {
|
|
339
|
+
throw new Error("Federation shutting down (mtr_shutdown announced).");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private async _resolveHandles(rti: RTIAmbassador): Promise<void> {
|
|
344
|
+
if (this._options.subscribeExecutionConfiguration) {
|
|
345
|
+
await this._executionConfiguration.resolve(rti);
|
|
346
|
+
}
|
|
347
|
+
if (this._options.subscribeReferenceFrame) {
|
|
348
|
+
await this._referenceFrame.resolve(rti);
|
|
349
|
+
}
|
|
350
|
+
if (this._options.subscribePhysicalEntity) {
|
|
351
|
+
await this._physicalEntity.resolve(rti);
|
|
352
|
+
}
|
|
353
|
+
if (this._options.subscribeDynamicalEntity) {
|
|
354
|
+
await this._dynamicalEntity.resolve(rti);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private async _subscribe(rti: RTIAmbassador): Promise<void> {
|
|
359
|
+
if (this._options.subscribeExecutionConfiguration) {
|
|
360
|
+
await this._executionConfiguration.subscribe(rti);
|
|
361
|
+
}
|
|
362
|
+
if (this._options.subscribeReferenceFrame) {
|
|
363
|
+
await this._referenceFrame.subscribe(rti);
|
|
364
|
+
}
|
|
365
|
+
if (this._options.subscribePhysicalEntity) {
|
|
366
|
+
await this._physicalEntity.subscribe(rti);
|
|
367
|
+
}
|
|
368
|
+
if (this._options.subscribeDynamicalEntity) {
|
|
369
|
+
await this._dynamicalEntity.subscribe(rti);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private async _requestAttributeUpdates(rti: RTIAmbassador): Promise<void> {
|
|
374
|
+
const tag = new Uint8Array();
|
|
375
|
+
if (this._options.subscribeExecutionConfiguration) {
|
|
376
|
+
await rti.requestClassAttributeValueUpdate(
|
|
377
|
+
this._executionConfiguration.classHandle,
|
|
378
|
+
this._executionConfiguration.attributeHandleSet,
|
|
379
|
+
tag
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (this._options.subscribeReferenceFrame) {
|
|
383
|
+
await rti.requestClassAttributeValueUpdate(
|
|
384
|
+
this._referenceFrame.classHandle,
|
|
385
|
+
this._referenceFrame.attributeHandleSet,
|
|
386
|
+
tag
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private _applyAttributes(objectInstance: ObjectInstanceHandle, values: AttributeHandleValueMap): void {
|
|
392
|
+
if (this._state.excoInstance && handleToHex(objectInstance) === handleToHex(this._state.excoInstance)) {
|
|
393
|
+
this._applyExcoAttributes(values);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const refFrameKey = handleToHex(objectInstance);
|
|
397
|
+
if (this._referenceFramesByInstance.has(refFrameKey)) {
|
|
398
|
+
this._applyReferenceFrameAttributes(refFrameKey, values);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private _applyExcoAttributes(values: AttributeHandleValueMap): void {
|
|
403
|
+
const decoded = this._executionConfiguration.decode(values) as Partial<ExecutionConfigurationAttributes>;
|
|
404
|
+
const previousCurrent = this._state.currentExecutionMode;
|
|
405
|
+
const previousNext = this._state.nextExecutionMode;
|
|
406
|
+
|
|
407
|
+
if (decoded.root_frame_name !== undefined) this._state.rootFrameName = decoded.root_frame_name;
|
|
408
|
+
if (decoded.scenario_time_epoch !== undefined) this._state.scenarioTimeEpoch = decoded.scenario_time_epoch;
|
|
409
|
+
if (decoded.current_execution_mode !== undefined) {
|
|
410
|
+
this._state.currentExecutionMode = decoded.current_execution_mode as SpaceFomExecutionMode;
|
|
411
|
+
}
|
|
412
|
+
if (decoded.next_execution_mode !== undefined) {
|
|
413
|
+
this._state.nextExecutionMode = decoded.next_execution_mode as SpaceFomExecutionMode;
|
|
414
|
+
}
|
|
415
|
+
if (decoded.next_mode_scenario_time !== undefined) this._state.nextModeScenarioTime = decoded.next_mode_scenario_time;
|
|
416
|
+
if (decoded.next_mode_cte_time !== undefined) this._state.nextModeCteTime = decoded.next_mode_cte_time;
|
|
417
|
+
if (decoded.least_common_time_step !== undefined) this._state.lctsMicros = decoded.least_common_time_step;
|
|
418
|
+
|
|
419
|
+
if (
|
|
420
|
+
(decoded.current_execution_mode !== undefined &&
|
|
421
|
+
decoded.current_execution_mode !== previousCurrent) ||
|
|
422
|
+
(decoded.next_execution_mode !== undefined &&
|
|
423
|
+
decoded.next_execution_mode !== previousNext)
|
|
424
|
+
) {
|
|
425
|
+
if (this._state.currentExecutionMode !== undefined) {
|
|
426
|
+
this._options.onExecutionModeChange?.({
|
|
427
|
+
current: this._state.currentExecutionMode,
|
|
428
|
+
next: this._state.nextExecutionMode,
|
|
429
|
+
previousCurrent,
|
|
430
|
+
previousNext,
|
|
431
|
+
state: this.state,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
this._options.onExecutionConfigurationUpdate?.({ state: this.state });
|
|
437
|
+
this._autoResolveInitializationIfNeeded();
|
|
438
|
+
this._checkExcoReady();
|
|
439
|
+
this._checkReferenceFrameTreeReady();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private _registerReferenceFrame(objectInstance: ObjectInstanceHandle, objectInstanceName?: string): void {
|
|
443
|
+
const key = handleToHex(objectInstance);
|
|
444
|
+
if (this._referenceFramesByInstance.has(key)) return;
|
|
445
|
+
const record: SpaceFomReferenceFrameRecord = {
|
|
446
|
+
key,
|
|
447
|
+
objectInstance,
|
|
448
|
+
name: objectInstanceName,
|
|
449
|
+
};
|
|
450
|
+
this._referenceFramesByInstance.set(key, record);
|
|
451
|
+
if (record.name) {
|
|
452
|
+
this._referenceFramesByName.set(record.name, record);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private _applyReferenceFrameAttributes(instanceKey: string, values: AttributeHandleValueMap): void {
|
|
457
|
+
const record = this._referenceFramesByInstance.get(instanceKey);
|
|
458
|
+
if (!record) return;
|
|
459
|
+
const decoded = this._referenceFrame.decode(values) as Partial<ReferenceFrameAttributes>;
|
|
460
|
+
const previousName = record.name;
|
|
461
|
+
|
|
462
|
+
if (decoded.name !== undefined) record.name = decoded.name;
|
|
463
|
+
if (decoded.parent_name !== undefined) record.parentName = decoded.parent_name;
|
|
464
|
+
if (decoded.state !== undefined) record.state = decoded.state;
|
|
465
|
+
|
|
466
|
+
if (record.name && record.name !== previousName) {
|
|
467
|
+
if (previousName) {
|
|
468
|
+
this._referenceFramesByName.delete(previousName);
|
|
469
|
+
}
|
|
470
|
+
this._referenceFramesByName.set(record.name, record);
|
|
471
|
+
} else if (record.name && !this._referenceFramesByName.has(record.name)) {
|
|
472
|
+
this._referenceFramesByName.set(record.name, record);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
this._checkReferenceFrameTreeReady();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private _checkExcoReady(): void {
|
|
479
|
+
if (this._excoReady) return;
|
|
480
|
+
const hasLcts = this._state.lctsMicros !== undefined;
|
|
481
|
+
const hasExecutionMode = this._state.currentExecutionMode !== undefined;
|
|
482
|
+
const hasRootFrame = !this._options.waitForReferenceFrameTree || !!this._state.rootFrameName;
|
|
483
|
+
if (hasLcts && hasExecutionMode && hasRootFrame) {
|
|
484
|
+
this._excoReady = true;
|
|
485
|
+
this._excoReadyResolve();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private _checkReferenceFrameTreeReady(): void {
|
|
490
|
+
if (this._referenceFrameTreeReady) return;
|
|
491
|
+
if (!this._options.waitForReferenceFrameTree) return;
|
|
492
|
+
const rootName = this._state.rootFrameName;
|
|
493
|
+
if (!rootName) return;
|
|
494
|
+
const rootFrame = this._referenceFramesByName.get(rootName);
|
|
495
|
+
if (!rootFrame) return;
|
|
496
|
+
|
|
497
|
+
for (const frame of this._referenceFramesByName.values()) {
|
|
498
|
+
if (!frame.name) continue;
|
|
499
|
+
let current: SpaceFomReferenceFrameRecord | undefined = frame;
|
|
500
|
+
const visited = new Set<string>();
|
|
501
|
+
while (current?.parentName) {
|
|
502
|
+
if (visited.has(current.parentName)) return;
|
|
503
|
+
visited.add(current.parentName);
|
|
504
|
+
const parent = this._referenceFramesByName.get(current.parentName);
|
|
505
|
+
if (!parent) return;
|
|
506
|
+
current = parent;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
this._referenceFrameTreeReady = true;
|
|
511
|
+
this._state.referenceFrameTreeReady = true;
|
|
512
|
+
this._referenceFrameReadyResolve();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private _autoResolveInitializationIfNeeded(): void {
|
|
516
|
+
if (this._initCompletedResolved || !this._options.autoCompleteInitialization) return;
|
|
517
|
+
const mode = this._state.currentExecutionMode;
|
|
518
|
+
if (
|
|
519
|
+
mode === undefined ||
|
|
520
|
+
mode === SpaceFomExecutionMode.Uninitialized ||
|
|
521
|
+
mode === SpaceFomExecutionMode.Initializing
|
|
522
|
+
) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
this._state.initializationCompletedAuto = true;
|
|
526
|
+
this._resolveInitializationCompleted();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private _resolveInitializationCompleted(): void {
|
|
530
|
+
if (this._initCompletedResolved) return;
|
|
531
|
+
this._initCompletedResolved = true;
|
|
532
|
+
this._initCompletedResolve();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private async _waitForReferenceFrameTree(): Promise<void> {
|
|
536
|
+
if (this._referenceFrameTreeReady) return;
|
|
537
|
+
const timeoutMs = this._options.referenceFrameTimeoutMs;
|
|
538
|
+
if (timeoutMs <= 0) {
|
|
539
|
+
await this._referenceFrameReadyPromise;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
await new Promise<void>((resolve, reject) => {
|
|
543
|
+
const timer = setTimeout(() => {
|
|
544
|
+
reject(new Error("Timed out waiting for reference frame tree."));
|
|
545
|
+
}, timeoutMs);
|
|
546
|
+
this._referenceFrameReadyPromise
|
|
547
|
+
.then(() => {
|
|
548
|
+
clearTimeout(timer);
|
|
549
|
+
resolve();
|
|
550
|
+
})
|
|
551
|
+
.catch((err) => {
|
|
552
|
+
clearTimeout(timer);
|
|
553
|
+
reject(err);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private async _enableTimeManagement(rti: RTIAmbassador): Promise<void> {
|
|
559
|
+
const lookahead: LogicalTimeInterval = encodeHLAinteger64Interval(this._options.lookaheadMicros);
|
|
560
|
+
await rti.enableTimeRegulation(lookahead);
|
|
561
|
+
await rti.enableTimeConstrained();
|
|
562
|
+
await Promise.all([this._timeRegulationPromise, this._timeConstrainedPromise]);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private async _advanceToHltb(rti: RTIAmbassador): Promise<void> {
|
|
566
|
+
if (!this._state.lctsMicros) {
|
|
567
|
+
throw new Error("ExCO least_common_time_step not received; cannot compute HLTB.");
|
|
568
|
+
}
|
|
569
|
+
const galt = await rti.queryGALT();
|
|
570
|
+
if (!galt.logicalTimeIsValid || !galt.logicalTime) {
|
|
571
|
+
throw new Error("GALT not valid; cannot compute HLTB.");
|
|
572
|
+
}
|
|
573
|
+
const galtMicros = decodeHLAinteger64Time(galt.logicalTime);
|
|
574
|
+
const hltbMicros = computeHltb(galtMicros, this._state.lctsMicros);
|
|
575
|
+
const time: LogicalTime = encodeHLAinteger64Time(hltbMicros);
|
|
576
|
+
|
|
577
|
+
if (this._options.awaitTimeAdvanceGrant) {
|
|
578
|
+
this._timeAdvancePromise = new Promise((resolve) => {
|
|
579
|
+
this._timeAdvanceResolve = resolve;
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
await rti.timeAdvanceRequest(time);
|
|
584
|
+
|
|
585
|
+
if (this._options.awaitTimeAdvanceGrant && this._timeAdvancePromise) {
|
|
586
|
+
await this._timeAdvancePromise;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export function computeHltb(galtMicros: bigint, lctsMicros: bigint): bigint {
|
|
592
|
+
if (lctsMicros <= 0n) {
|
|
593
|
+
throw new Error("LCTS must be positive.");
|
|
594
|
+
}
|
|
595
|
+
return ((galtMicros / lctsMicros) + 1n) * lctsMicros;
|
|
596
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { parseSpacekitEnv, type SpacekitEnvDefaults } from "./env.ts";
|
|
2
|
+
import type { SpacekitAppOptions } from "./see-app.ts";
|
|
3
|
+
|
|
4
|
+
export interface SpaceFomConfigOptions<T extends Record<string, unknown>> {
|
|
5
|
+
env?: Record<string, string | undefined>;
|
|
6
|
+
envDefaults?: SpacekitEnvDefaults;
|
|
7
|
+
app?: Partial<SpacekitAppOptions>;
|
|
8
|
+
extras?: (env: Record<string, string | undefined>) => T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createSpaceFomConfig<T extends Record<string, unknown> = Record<string, never>>(
|
|
12
|
+
options: SpaceFomConfigOptions<T> = {}
|
|
13
|
+
): { app: SpacekitAppOptions } & T {
|
|
14
|
+
const env = options.env ?? process.env;
|
|
15
|
+
const base = parseSpacekitEnv({
|
|
16
|
+
env,
|
|
17
|
+
defaults: options.envDefaults,
|
|
18
|
+
});
|
|
19
|
+
const app: SpacekitAppOptions = {
|
|
20
|
+
...base,
|
|
21
|
+
...options.app,
|
|
22
|
+
};
|
|
23
|
+
const extras = options.extras ? options.extras(env) : ({} as T);
|
|
24
|
+
return { app, ...extras };
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { buildDeclarativeFomFromDecorators } from "./decorators.ts";
|
|
3
|
+
import { ModeTransitionRequest } from "./spacefom-interactions.ts";
|
|
4
|
+
import { PhysicalEntity } from "./spacefom-entities.ts";
|
|
5
|
+
|
|
6
|
+
test("ModeTransitionRequest decorator metadata builds schema", () => {
|
|
7
|
+
const declarative = buildDeclarativeFomFromDecorators({
|
|
8
|
+
interactionClasses: [ModeTransitionRequest],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const schema = declarative.getInteractionSchema<{ execution_mode: number }>(
|
|
12
|
+
"HLAinteractionRoot.ModeTransitionRequest"
|
|
13
|
+
);
|
|
14
|
+
expect(schema?.parameters.execution_mode?.dataType).toBe("HLAinteger16LE");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("PhysicalEntity decorator metadata builds canonical schema", () => {
|
|
18
|
+
const declarative = buildDeclarativeFomFromDecorators({
|
|
19
|
+
objectClasses: [PhysicalEntity],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const schema = declarative.getObjectSchema<{ parent_reference_frame: string; state: unknown }>(
|
|
23
|
+
"HLAobjectRoot.PhysicalEntity"
|
|
24
|
+
);
|
|
25
|
+
expect(schema?.attributes.parent_reference_frame?.dataType).toBe("HLAunicodeString");
|
|
26
|
+
expect(schema?.attributes.state?.dataType).toBe("SpaceTimeCoordinateState");
|
|
27
|
+
});
|