@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,541 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AttributeHandle,
|
|
3
|
+
AttributeHandleValueMap,
|
|
4
|
+
ConveyedRegionSet,
|
|
5
|
+
FederateHandle,
|
|
6
|
+
InteractionClassHandle,
|
|
7
|
+
LogicalTime,
|
|
8
|
+
MessageRetractionHandle,
|
|
9
|
+
ObjectClassHandle,
|
|
10
|
+
ObjectInstanceHandle,
|
|
11
|
+
OrderType,
|
|
12
|
+
ParameterHandleValueMap,
|
|
13
|
+
RTIAmbassador,
|
|
14
|
+
TransportationTypeHandle,
|
|
15
|
+
UserSuppliedTag,
|
|
16
|
+
} from "@hla4ts/hla-api";
|
|
17
|
+
import { handleToHex } from "@hla4ts/hla-api";
|
|
18
|
+
import type { SharingType } from "@hla4ts/fom-codegen";
|
|
19
|
+
import type { DeclarativeFom, DeclarativeObjectClass } from "./declarative.ts";
|
|
20
|
+
import { InteractionClassAdapter, ObjectClassAdapter, ObjectInstanceAdapter } from "./object-model.ts";
|
|
21
|
+
import type { FederateHandlers } from "./types.ts";
|
|
22
|
+
|
|
23
|
+
export interface DeclarativeRuntimeOptions {
|
|
24
|
+
autoPublish?: boolean;
|
|
25
|
+
autoSubscribe?: boolean;
|
|
26
|
+
defaultInteractionSharing?: SharingType;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DeclarativePublicationSummary {
|
|
30
|
+
objectClasses: {
|
|
31
|
+
publish: string[];
|
|
32
|
+
subscribe: string[];
|
|
33
|
+
};
|
|
34
|
+
interactions: {
|
|
35
|
+
publish: string[];
|
|
36
|
+
subscribe: string[];
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DeclarativeObjectDiscoverContext<T extends object> {
|
|
41
|
+
instance: ObjectInstanceAdapter<T>;
|
|
42
|
+
objectClass: ObjectClassHandle;
|
|
43
|
+
objectInstanceName: string;
|
|
44
|
+
producingFederate: FederateHandle;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DeclarativeObjectUpdateContext<T extends object> {
|
|
48
|
+
instance: ObjectInstanceAdapter<T>;
|
|
49
|
+
values: Partial<T>;
|
|
50
|
+
rawValues: AttributeHandleValueMap;
|
|
51
|
+
userSuppliedTag: UserSuppliedTag;
|
|
52
|
+
transportationType?: TransportationTypeHandle;
|
|
53
|
+
producingFederate: FederateHandle;
|
|
54
|
+
sentRegions?: ConveyedRegionSet;
|
|
55
|
+
time?: LogicalTime;
|
|
56
|
+
sentOrderType?: OrderType;
|
|
57
|
+
receivedOrderType?: OrderType;
|
|
58
|
+
retractionHandle?: MessageRetractionHandle;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface DeclarativeObjectRemoveContext<T extends object> {
|
|
62
|
+
instance: ObjectInstanceAdapter<T>;
|
|
63
|
+
userSuppliedTag: UserSuppliedTag;
|
|
64
|
+
producingFederate: FederateHandle;
|
|
65
|
+
time?: LogicalTime;
|
|
66
|
+
sentOrderType?: OrderType;
|
|
67
|
+
receivedOrderType?: OrderType;
|
|
68
|
+
retractionHandle?: MessageRetractionHandle;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface DeclarativeInteractionContext<T extends object> {
|
|
72
|
+
interactionClass: InteractionClassHandle;
|
|
73
|
+
values: Partial<T>;
|
|
74
|
+
rawValues: ParameterHandleValueMap;
|
|
75
|
+
userSuppliedTag: UserSuppliedTag;
|
|
76
|
+
transportationType?: TransportationTypeHandle;
|
|
77
|
+
producingFederate: FederateHandle;
|
|
78
|
+
sentRegions?: ConveyedRegionSet;
|
|
79
|
+
time?: LogicalTime;
|
|
80
|
+
sentOrderType?: OrderType;
|
|
81
|
+
receivedOrderType?: OrderType;
|
|
82
|
+
retractionHandle?: MessageRetractionHandle;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DeclarativeObjectHandlers<T extends object> {
|
|
86
|
+
discover?: (context: DeclarativeObjectDiscoverContext<T>) => void;
|
|
87
|
+
update?: (context: DeclarativeObjectUpdateContext<T>) => void;
|
|
88
|
+
remove?: (context: DeclarativeObjectRemoveContext<T>) => void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface DeclarativeInteractionHandlers<T extends object> {
|
|
92
|
+
receive?: (context: DeclarativeInteractionContext<T>) => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const DEFAULT_INTERACTION_SHARING: SharingType = "PublishSubscribe";
|
|
96
|
+
|
|
97
|
+
function canPublish(sharing?: SharingType, defaultSharing: SharingType = DEFAULT_INTERACTION_SHARING): boolean {
|
|
98
|
+
const resolved = sharing ?? defaultSharing;
|
|
99
|
+
return resolved === "Publish" || resolved === "PublishSubscribe";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function canSubscribe(sharing?: SharingType, defaultSharing: SharingType = DEFAULT_INTERACTION_SHARING): boolean {
|
|
103
|
+
const resolved = sharing ?? defaultSharing;
|
|
104
|
+
return resolved === "Subscribe" || resolved === "PublishSubscribe";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class DeclarativeRuntime {
|
|
108
|
+
readonly handlers: FederateHandlers;
|
|
109
|
+
private readonly _fom: DeclarativeFom;
|
|
110
|
+
private readonly _options: Required<DeclarativeRuntimeOptions>;
|
|
111
|
+
private readonly _objectHandlers = new Map<
|
|
112
|
+
string,
|
|
113
|
+
Array<DeclarativeObjectHandlers<Record<string, unknown>>>
|
|
114
|
+
>();
|
|
115
|
+
private readonly _interactionHandlers = new Map<
|
|
116
|
+
string,
|
|
117
|
+
Array<DeclarativeInteractionHandlers<Record<string, unknown>>>
|
|
118
|
+
>();
|
|
119
|
+
private readonly _objectInstances = new Map<string, ObjectInstanceAdapter<Record<string, unknown>>>();
|
|
120
|
+
private readonly _objectClassByHandle = new Map<string, string>();
|
|
121
|
+
private readonly _interactionClassByHandle = new Map<string, string>();
|
|
122
|
+
|
|
123
|
+
constructor(fom: DeclarativeFom, options: DeclarativeRuntimeOptions = {}) {
|
|
124
|
+
this._fom = fom;
|
|
125
|
+
this._options = {
|
|
126
|
+
autoPublish: options.autoPublish ?? true,
|
|
127
|
+
autoSubscribe: options.autoSubscribe ?? true,
|
|
128
|
+
defaultInteractionSharing: options.defaultInteractionSharing ?? DEFAULT_INTERACTION_SHARING,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
this.handlers = {
|
|
132
|
+
discoverObjectInstance: (objectInstance, objectClass, objectInstanceName, producingFederate) => {
|
|
133
|
+
const className = this._objectClassByHandle.get(handleToHex(objectClass));
|
|
134
|
+
if (!className) return;
|
|
135
|
+
const adapter = this._fom.getObjectAdapter<Record<string, unknown>>(className);
|
|
136
|
+
if (!adapter) return;
|
|
137
|
+
const key = handleToHex(objectInstance);
|
|
138
|
+
const existing = this._objectInstances.get(key);
|
|
139
|
+
const instance =
|
|
140
|
+
existing ?? new ObjectInstanceAdapter<Record<string, unknown>>(adapter, objectInstance);
|
|
141
|
+
instance.objectInstanceName = objectInstanceName;
|
|
142
|
+
this._objectInstances.set(key, instance);
|
|
143
|
+
const handlers = this._objectHandlers.get(className);
|
|
144
|
+
if (!handlers) return;
|
|
145
|
+
for (const handler of handlers) {
|
|
146
|
+
handler.discover?.({
|
|
147
|
+
instance: instance as ObjectInstanceAdapter<Record<string, unknown>>,
|
|
148
|
+
objectClass,
|
|
149
|
+
objectInstanceName,
|
|
150
|
+
producingFederate,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
reflectAttributeValues: (
|
|
155
|
+
objectInstance,
|
|
156
|
+
attributeValues,
|
|
157
|
+
userSuppliedTag,
|
|
158
|
+
transportationType,
|
|
159
|
+
producingFederate,
|
|
160
|
+
sentRegions
|
|
161
|
+
) => {
|
|
162
|
+
this._handleAttributeUpdate({
|
|
163
|
+
objectInstance,
|
|
164
|
+
attributeValues,
|
|
165
|
+
userSuppliedTag,
|
|
166
|
+
transportationType,
|
|
167
|
+
producingFederate,
|
|
168
|
+
sentRegions,
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
reflectAttributeValuesWithTime: (
|
|
172
|
+
objectInstance,
|
|
173
|
+
attributeValues,
|
|
174
|
+
userSuppliedTag,
|
|
175
|
+
transportationType,
|
|
176
|
+
producingFederate,
|
|
177
|
+
sentRegions,
|
|
178
|
+
time,
|
|
179
|
+
sentOrderType,
|
|
180
|
+
receivedOrderType,
|
|
181
|
+
retractionHandle
|
|
182
|
+
) => {
|
|
183
|
+
this._handleAttributeUpdate({
|
|
184
|
+
objectInstance,
|
|
185
|
+
attributeValues,
|
|
186
|
+
userSuppliedTag,
|
|
187
|
+
transportationType,
|
|
188
|
+
producingFederate,
|
|
189
|
+
sentRegions,
|
|
190
|
+
time,
|
|
191
|
+
sentOrderType,
|
|
192
|
+
receivedOrderType,
|
|
193
|
+
retractionHandle,
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
removeObjectInstance: (objectInstance, userSuppliedTag, producingFederate) => {
|
|
197
|
+
this._handleObjectRemoval({
|
|
198
|
+
objectInstance,
|
|
199
|
+
userSuppliedTag,
|
|
200
|
+
producingFederate,
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
removeObjectInstanceWithTime: (
|
|
204
|
+
objectInstance,
|
|
205
|
+
userSuppliedTag,
|
|
206
|
+
producingFederate,
|
|
207
|
+
time,
|
|
208
|
+
sentOrderType,
|
|
209
|
+
receivedOrderType,
|
|
210
|
+
retractionHandle
|
|
211
|
+
) => {
|
|
212
|
+
this._handleObjectRemoval({
|
|
213
|
+
objectInstance,
|
|
214
|
+
userSuppliedTag,
|
|
215
|
+
producingFederate,
|
|
216
|
+
time,
|
|
217
|
+
sentOrderType,
|
|
218
|
+
receivedOrderType,
|
|
219
|
+
retractionHandle,
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
receiveInteraction: (
|
|
223
|
+
interactionClass,
|
|
224
|
+
parameterValues,
|
|
225
|
+
userSuppliedTag,
|
|
226
|
+
transportationType,
|
|
227
|
+
producingFederate,
|
|
228
|
+
sentRegions
|
|
229
|
+
) => {
|
|
230
|
+
this._handleInteraction({
|
|
231
|
+
interactionClass,
|
|
232
|
+
parameterValues,
|
|
233
|
+
userSuppliedTag,
|
|
234
|
+
transportationType,
|
|
235
|
+
producingFederate,
|
|
236
|
+
sentRegions,
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
receiveInteractionWithTime: (
|
|
240
|
+
interactionClass,
|
|
241
|
+
parameterValues,
|
|
242
|
+
userSuppliedTag,
|
|
243
|
+
transportationType,
|
|
244
|
+
producingFederate,
|
|
245
|
+
sentRegions,
|
|
246
|
+
time,
|
|
247
|
+
sentOrderType,
|
|
248
|
+
receivedOrderType,
|
|
249
|
+
retractionHandle
|
|
250
|
+
) => {
|
|
251
|
+
this._handleInteraction({
|
|
252
|
+
interactionClass,
|
|
253
|
+
parameterValues,
|
|
254
|
+
userSuppliedTag,
|
|
255
|
+
transportationType,
|
|
256
|
+
producingFederate,
|
|
257
|
+
sentRegions,
|
|
258
|
+
time,
|
|
259
|
+
sentOrderType,
|
|
260
|
+
receivedOrderType,
|
|
261
|
+
retractionHandle,
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
registerObjectHandlers<T extends object>(
|
|
268
|
+
adapter: ObjectClassAdapter<T>,
|
|
269
|
+
handlers: DeclarativeObjectHandlers<T>
|
|
270
|
+
): void {
|
|
271
|
+
this._objectHandlers.set(adapter.className, [
|
|
272
|
+
handlers as DeclarativeObjectHandlers<Record<string, unknown>>,
|
|
273
|
+
]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
registerInteractionHandlers<T extends object>(
|
|
277
|
+
adapter: InteractionClassAdapter<T>,
|
|
278
|
+
handlers: DeclarativeInteractionHandlers<T>
|
|
279
|
+
): void {
|
|
280
|
+
this._interactionHandlers.set(adapter.className, [
|
|
281
|
+
handlers as DeclarativeInteractionHandlers<Record<string, unknown>>,
|
|
282
|
+
]);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
addObjectHandlers<T extends object>(
|
|
286
|
+
adapter: ObjectClassAdapter<T>,
|
|
287
|
+
handlers: DeclarativeObjectHandlers<T>
|
|
288
|
+
): void {
|
|
289
|
+
const existing = this._objectHandlers.get(adapter.className) ?? [];
|
|
290
|
+
existing.push(handlers as DeclarativeObjectHandlers<Record<string, unknown>>);
|
|
291
|
+
this._objectHandlers.set(adapter.className, existing);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
addInteractionHandlers<T extends object>(
|
|
295
|
+
adapter: InteractionClassAdapter<T>,
|
|
296
|
+
handlers: DeclarativeInteractionHandlers<T>
|
|
297
|
+
): void {
|
|
298
|
+
const existing = this._interactionHandlers.get(adapter.className) ?? [];
|
|
299
|
+
existing.push(handlers as DeclarativeInteractionHandlers<Record<string, unknown>>);
|
|
300
|
+
this._interactionHandlers.set(adapter.className, existing);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
getObjectInstance<T extends object>(
|
|
304
|
+
objectInstance: ObjectInstanceHandle
|
|
305
|
+
): ObjectInstanceAdapter<T> | undefined {
|
|
306
|
+
return this._objectInstances.get(handleToHex(objectInstance)) as
|
|
307
|
+
| ObjectInstanceAdapter<T>
|
|
308
|
+
| undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async resolve(rti: RTIAmbassador): Promise<void> {
|
|
312
|
+
await this._fom.resolveAll(rti);
|
|
313
|
+
this._refreshHandleMaps();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async publish(rti: RTIAmbassador): Promise<void> {
|
|
317
|
+
for (const [name, adapter] of this._fom.objectAdapters) {
|
|
318
|
+
const schema = this._fom.getObjectSchema<Record<string, unknown>>(name);
|
|
319
|
+
if (!schema) continue;
|
|
320
|
+
if (!adapter.isResolved) {
|
|
321
|
+
await adapter.resolve(rti);
|
|
322
|
+
}
|
|
323
|
+
const handles = collectAttributeHandles(schema, adapter, (sharing) =>
|
|
324
|
+
canPublish(sharing)
|
|
325
|
+
);
|
|
326
|
+
if (handles.length === 0) continue;
|
|
327
|
+
await rti.publishObjectClassAttributes(adapter.classHandle, handles);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
for (const [name, adapter] of this._fom.interactionAdapters) {
|
|
331
|
+
const schema = this._fom.getInteractionSchema<Record<string, unknown>>(name);
|
|
332
|
+
if (!schema) continue;
|
|
333
|
+
if (!adapter.isResolved) {
|
|
334
|
+
await adapter.resolve(rti);
|
|
335
|
+
}
|
|
336
|
+
if (!canPublish(schema.sharing, this._options.defaultInteractionSharing)) continue;
|
|
337
|
+
await rti.publishInteractionClass(adapter.interactionHandle);
|
|
338
|
+
}
|
|
339
|
+
this._refreshHandleMaps();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async subscribe(rti: RTIAmbassador): Promise<void> {
|
|
343
|
+
for (const [name, adapter] of this._fom.objectAdapters) {
|
|
344
|
+
const schema = this._fom.getObjectSchema<Record<string, unknown>>(name);
|
|
345
|
+
if (!schema) continue;
|
|
346
|
+
if (!adapter.isResolved) {
|
|
347
|
+
await adapter.resolve(rti);
|
|
348
|
+
}
|
|
349
|
+
const handles = collectAttributeHandles(schema, adapter, (sharing) =>
|
|
350
|
+
canSubscribe(sharing)
|
|
351
|
+
);
|
|
352
|
+
if (handles.length === 0) continue;
|
|
353
|
+
await rti.subscribeObjectClassAttributes(adapter.classHandle, handles);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
for (const [name, adapter] of this._fom.interactionAdapters) {
|
|
357
|
+
const schema = this._fom.getInteractionSchema<Record<string, unknown>>(name);
|
|
358
|
+
if (!schema) continue;
|
|
359
|
+
if (!adapter.isResolved) {
|
|
360
|
+
await adapter.resolve(rti);
|
|
361
|
+
}
|
|
362
|
+
if (!canSubscribe(schema.sharing, this._options.defaultInteractionSharing)) continue;
|
|
363
|
+
await rti.subscribeInteractionClass(adapter.interactionHandle);
|
|
364
|
+
}
|
|
365
|
+
this._refreshHandleMaps();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async prepare(rti: RTIAmbassador): Promise<void> {
|
|
369
|
+
await this.resolve(rti);
|
|
370
|
+
if (this._options.autoPublish) {
|
|
371
|
+
await this.publish(rti);
|
|
372
|
+
}
|
|
373
|
+
if (this._options.autoSubscribe) {
|
|
374
|
+
await this.subscribe(rti);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
getPublicationSummary(): DeclarativePublicationSummary {
|
|
379
|
+
const objectPublish: string[] = [];
|
|
380
|
+
const objectSubscribe: string[] = [];
|
|
381
|
+
const interactionPublish: string[] = [];
|
|
382
|
+
const interactionSubscribe: string[] = [];
|
|
383
|
+
|
|
384
|
+
for (const [name] of this._fom.objectAdapters) {
|
|
385
|
+
const schema = this._fom.getObjectSchema<Record<string, unknown>>(name);
|
|
386
|
+
if (!schema) continue;
|
|
387
|
+
const attributes = Object.values(schema.attributes ?? {});
|
|
388
|
+
const canPub = attributes.some((attribute) => canPublish(attribute.sharing));
|
|
389
|
+
const canSub = attributes.some((attribute) => canSubscribe(attribute.sharing));
|
|
390
|
+
if (canPub) objectPublish.push(name);
|
|
391
|
+
if (canSub) objectSubscribe.push(name);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
for (const [name] of this._fom.interactionAdapters) {
|
|
395
|
+
const schema = this._fom.getInteractionSchema<Record<string, unknown>>(name);
|
|
396
|
+
if (!schema) continue;
|
|
397
|
+
if (canPublish(schema.sharing, this._options.defaultInteractionSharing)) {
|
|
398
|
+
interactionPublish.push(name);
|
|
399
|
+
}
|
|
400
|
+
if (canSubscribe(schema.sharing, this._options.defaultInteractionSharing)) {
|
|
401
|
+
interactionSubscribe.push(name);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
objectClasses: { publish: objectPublish, subscribe: objectSubscribe },
|
|
407
|
+
interactions: { publish: interactionPublish, subscribe: interactionSubscribe },
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private _refreshHandleMaps(): void {
|
|
412
|
+
this._objectClassByHandle.clear();
|
|
413
|
+
this._interactionClassByHandle.clear();
|
|
414
|
+
|
|
415
|
+
for (const [name, adapter] of this._fom.objectAdapters) {
|
|
416
|
+
if (!adapter.isResolved) continue;
|
|
417
|
+
this._objectClassByHandle.set(handleToHex(adapter.classHandle), name);
|
|
418
|
+
}
|
|
419
|
+
for (const [name, adapter] of this._fom.interactionAdapters) {
|
|
420
|
+
if (!adapter.isResolved) continue;
|
|
421
|
+
this._interactionClassByHandle.set(handleToHex(adapter.interactionHandle), name);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private _handleAttributeUpdate(params: {
|
|
426
|
+
objectInstance: ObjectInstanceHandle;
|
|
427
|
+
attributeValues: AttributeHandleValueMap;
|
|
428
|
+
userSuppliedTag: UserSuppliedTag;
|
|
429
|
+
transportationType?: TransportationTypeHandle;
|
|
430
|
+
producingFederate: FederateHandle;
|
|
431
|
+
sentRegions?: ConveyedRegionSet;
|
|
432
|
+
time?: LogicalTime;
|
|
433
|
+
sentOrderType?: OrderType;
|
|
434
|
+
receivedOrderType?: OrderType;
|
|
435
|
+
retractionHandle?: MessageRetractionHandle;
|
|
436
|
+
}): void {
|
|
437
|
+
const key = handleToHex(params.objectInstance);
|
|
438
|
+
const instance = this._objectInstances.get(key);
|
|
439
|
+
if (!instance) return;
|
|
440
|
+
const adapter = instance.objectClass;
|
|
441
|
+
const values = adapter.decode(params.attributeValues);
|
|
442
|
+
const handlers = this._objectHandlers.get(adapter.className);
|
|
443
|
+
if (!handlers) return;
|
|
444
|
+
for (const handler of handlers) {
|
|
445
|
+
handler.update?.({
|
|
446
|
+
instance: instance as ObjectInstanceAdapter<Record<string, unknown>>,
|
|
447
|
+
values: values as Partial<Record<string, unknown>>,
|
|
448
|
+
rawValues: params.attributeValues,
|
|
449
|
+
userSuppliedTag: params.userSuppliedTag,
|
|
450
|
+
transportationType: params.transportationType,
|
|
451
|
+
producingFederate: params.producingFederate,
|
|
452
|
+
sentRegions: params.sentRegions,
|
|
453
|
+
time: params.time,
|
|
454
|
+
sentOrderType: params.sentOrderType,
|
|
455
|
+
receivedOrderType: params.receivedOrderType,
|
|
456
|
+
retractionHandle: params.retractionHandle,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private _handleObjectRemoval(params: {
|
|
462
|
+
objectInstance: ObjectInstanceHandle;
|
|
463
|
+
userSuppliedTag: UserSuppliedTag;
|
|
464
|
+
producingFederate: FederateHandle;
|
|
465
|
+
time?: LogicalTime;
|
|
466
|
+
sentOrderType?: OrderType;
|
|
467
|
+
receivedOrderType?: OrderType;
|
|
468
|
+
retractionHandle?: MessageRetractionHandle;
|
|
469
|
+
}): void {
|
|
470
|
+
const key = handleToHex(params.objectInstance);
|
|
471
|
+
const instance = this._objectInstances.get(key);
|
|
472
|
+
if (!instance) return;
|
|
473
|
+
const handlers = this._objectHandlers.get(instance.className);
|
|
474
|
+
if (handlers) {
|
|
475
|
+
for (const handler of handlers) {
|
|
476
|
+
handler.remove?.({
|
|
477
|
+
instance: instance as ObjectInstanceAdapter<Record<string, unknown>>,
|
|
478
|
+
userSuppliedTag: params.userSuppliedTag,
|
|
479
|
+
producingFederate: params.producingFederate,
|
|
480
|
+
time: params.time,
|
|
481
|
+
sentOrderType: params.sentOrderType,
|
|
482
|
+
receivedOrderType: params.receivedOrderType,
|
|
483
|
+
retractionHandle: params.retractionHandle,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
this._objectInstances.delete(key);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private _handleInteraction(params: {
|
|
491
|
+
interactionClass: InteractionClassHandle;
|
|
492
|
+
parameterValues: ParameterHandleValueMap;
|
|
493
|
+
userSuppliedTag: UserSuppliedTag;
|
|
494
|
+
transportationType?: TransportationTypeHandle;
|
|
495
|
+
producingFederate: FederateHandle;
|
|
496
|
+
sentRegions?: ConveyedRegionSet;
|
|
497
|
+
time?: LogicalTime;
|
|
498
|
+
sentOrderType?: OrderType;
|
|
499
|
+
receivedOrderType?: OrderType;
|
|
500
|
+
retractionHandle?: MessageRetractionHandle;
|
|
501
|
+
}): void {
|
|
502
|
+
const className = this._interactionClassByHandle.get(handleToHex(params.interactionClass));
|
|
503
|
+
if (!className) return;
|
|
504
|
+
const adapter = this._fom.getInteractionAdapter<Record<string, unknown>>(className);
|
|
505
|
+
if (!adapter) return;
|
|
506
|
+
const values = adapter.decode(params.parameterValues);
|
|
507
|
+
const handlers = this._interactionHandlers.get(className);
|
|
508
|
+
if (!handlers) return;
|
|
509
|
+
for (const handler of handlers) {
|
|
510
|
+
handler.receive?.({
|
|
511
|
+
interactionClass: params.interactionClass,
|
|
512
|
+
values: values as Partial<Record<string, unknown>>,
|
|
513
|
+
rawValues: params.parameterValues,
|
|
514
|
+
userSuppliedTag: params.userSuppliedTag,
|
|
515
|
+
transportationType: params.transportationType,
|
|
516
|
+
producingFederate: params.producingFederate,
|
|
517
|
+
sentRegions: params.sentRegions,
|
|
518
|
+
time: params.time,
|
|
519
|
+
sentOrderType: params.sentOrderType,
|
|
520
|
+
receivedOrderType: params.receivedOrderType,
|
|
521
|
+
retractionHandle: params.retractionHandle,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function collectAttributeHandles<T extends object>(
|
|
528
|
+
schema: DeclarativeObjectClass<T>,
|
|
529
|
+
adapter: ObjectClassAdapter<T>,
|
|
530
|
+
predicate: (sharing: SharingType) => boolean
|
|
531
|
+
): AttributeHandle[] {
|
|
532
|
+
const handles: AttributeHandle[] = [];
|
|
533
|
+
const entries = Object.entries(schema.attributes) as Array<
|
|
534
|
+
[keyof T & string, DeclarativeObjectClass<T>["attributes"][keyof T]]
|
|
535
|
+
>;
|
|
536
|
+
for (const [key, attribute] of entries) {
|
|
537
|
+
if (!predicate(attribute.sharing)) continue;
|
|
538
|
+
handles.push(adapter.getAttributeHandle(key));
|
|
539
|
+
}
|
|
540
|
+
return handles;
|
|
541
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { DeclarativeFom } from "./declarative.ts";
|
|
3
|
+
|
|
4
|
+
test("DeclarativeFom registers object and interaction schemas", () => {
|
|
5
|
+
const declarative = new DeclarativeFom();
|
|
6
|
+
|
|
7
|
+
declarative.defineObjectClass<{ name: string }>({
|
|
8
|
+
name: "HLAobjectRoot.TestEntity",
|
|
9
|
+
sharing: "PublishSubscribe",
|
|
10
|
+
attributes: {
|
|
11
|
+
name: {
|
|
12
|
+
name: "name",
|
|
13
|
+
dataType: "HLAunicodeString",
|
|
14
|
+
updateType: "Static",
|
|
15
|
+
updateCondition: "during initialization",
|
|
16
|
+
ownership: "NoTransfer",
|
|
17
|
+
sharing: "PublishSubscribe",
|
|
18
|
+
transportation: "HLAreliable",
|
|
19
|
+
order: "TimeStamp",
|
|
20
|
+
encode: (value) => new TextEncoder().encode(value),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
declarative.defineInteractionClass<{ count: number }>({
|
|
26
|
+
name: "HLAinteractionRoot.TestInteraction",
|
|
27
|
+
sharing: "PublishSubscribe",
|
|
28
|
+
transportation: "HLAreliable",
|
|
29
|
+
order: "Receive",
|
|
30
|
+
parameters: {
|
|
31
|
+
count: {
|
|
32
|
+
name: "count",
|
|
33
|
+
dataType: "HLAinteger32BE",
|
|
34
|
+
encode: (value) => {
|
|
35
|
+
const buffer = new ArrayBuffer(4);
|
|
36
|
+
const view = new DataView(buffer);
|
|
37
|
+
view.setInt32(0, value, false);
|
|
38
|
+
return new Uint8Array(buffer);
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const model = declarative.registry.buildModel();
|
|
45
|
+
expect(model.objectClasses[0]?.name).toBe("HLAobjectRoot.TestEntity");
|
|
46
|
+
expect(model.objectClasses[0]?.attributes?.[0]?.name).toBe("name");
|
|
47
|
+
expect(model.interactionClasses[0]?.name).toBe("HLAinteractionRoot.TestInteraction");
|
|
48
|
+
expect(model.interactionClasses[0]?.parameters?.[0]?.name).toBe("count");
|
|
49
|
+
});
|