@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,254 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AttributeSchema,
|
|
3
|
+
InteractionClassSchema,
|
|
4
|
+
ObjectClassSchema,
|
|
5
|
+
ParameterSchema,
|
|
6
|
+
SharingType,
|
|
7
|
+
OrderType,
|
|
8
|
+
UpdateType,
|
|
9
|
+
OwnershipType,
|
|
10
|
+
} from "@hla4ts/fom-codegen";
|
|
11
|
+
import { FomRegistry } from "@hla4ts/fom-codegen";
|
|
12
|
+
import type { RTIAmbassador } from "@hla4ts/hla-api";
|
|
13
|
+
import { InteractionClassAdapter, ObjectClassAdapter } from "./object-model.ts";
|
|
14
|
+
|
|
15
|
+
export type DeclarativeAttribute<T> = AttributeSchema & {
|
|
16
|
+
encode: (value: T) => Uint8Array;
|
|
17
|
+
decode?: (data: Uint8Array) => T;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface DeclarativeObjectClass<T extends object> {
|
|
21
|
+
name: string;
|
|
22
|
+
parent?: string;
|
|
23
|
+
sharing?: SharingType;
|
|
24
|
+
semantics?: string;
|
|
25
|
+
attributes: {
|
|
26
|
+
[K in keyof T]: DeclarativeAttribute<T[K]>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type DeclarativeParameter<T> = ParameterSchema & {
|
|
31
|
+
encode: (value: T) => Uint8Array;
|
|
32
|
+
decode?: (data: Uint8Array) => T;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface DeclarativeInteractionClass<T extends object> {
|
|
36
|
+
name: string;
|
|
37
|
+
parent?: string;
|
|
38
|
+
sharing?: SharingType;
|
|
39
|
+
transportation?: string;
|
|
40
|
+
order?: OrderType;
|
|
41
|
+
semantics?: string;
|
|
42
|
+
parameters: {
|
|
43
|
+
[K in keyof T]: DeclarativeParameter<T[K]>;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DeclarativeFomOptions {
|
|
48
|
+
registry?: FomRegistry;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class DeclarativeFom {
|
|
52
|
+
readonly registry: FomRegistry;
|
|
53
|
+
private readonly _objectAdapters = new Map<string, ObjectClassAdapter<Record<string, unknown>>>();
|
|
54
|
+
private readonly _interactionAdapters = new Map<
|
|
55
|
+
string,
|
|
56
|
+
InteractionClassAdapter<Record<string, unknown>>
|
|
57
|
+
>();
|
|
58
|
+
private readonly _objectSchemas = new Map<string, DeclarativeObjectClass<Record<string, unknown>>>();
|
|
59
|
+
private readonly _interactionSchemas = new Map<
|
|
60
|
+
string,
|
|
61
|
+
DeclarativeInteractionClass<Record<string, unknown>>
|
|
62
|
+
>();
|
|
63
|
+
|
|
64
|
+
constructor(options: DeclarativeFomOptions = {}) {
|
|
65
|
+
this.registry = options.registry ?? new FomRegistry();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
defineObjectClass<T extends object>(
|
|
69
|
+
schema: DeclarativeObjectClass<T>,
|
|
70
|
+
options: { register?: boolean } = {}
|
|
71
|
+
): ObjectClassAdapter<T> {
|
|
72
|
+
const resolvedName = resolveHierarchicalName(schema.name, schema.parent);
|
|
73
|
+
const adapter = new ObjectClassAdapter<T>(resolvedName, mapDeclarativeAttributes(schema));
|
|
74
|
+
if (options.register !== false) {
|
|
75
|
+
this.registry.addObjectClass(toObjectClassSchema(schema));
|
|
76
|
+
}
|
|
77
|
+
this._objectAdapters.set(
|
|
78
|
+
resolvedName,
|
|
79
|
+
adapter as ObjectClassAdapter<Record<string, unknown>>
|
|
80
|
+
);
|
|
81
|
+
this._objectSchemas.set(
|
|
82
|
+
resolvedName,
|
|
83
|
+
schema as DeclarativeObjectClass<Record<string, unknown>>
|
|
84
|
+
);
|
|
85
|
+
return adapter;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
defineInteractionClass<T extends object>(
|
|
89
|
+
schema: DeclarativeInteractionClass<T>,
|
|
90
|
+
options: { register?: boolean } = {}
|
|
91
|
+
): InteractionClassAdapter<T> {
|
|
92
|
+
const resolvedName = resolveHierarchicalName(schema.name, schema.parent);
|
|
93
|
+
const adapter = new InteractionClassAdapter<T>(
|
|
94
|
+
resolvedName,
|
|
95
|
+
mapDeclarativeParameters(schema)
|
|
96
|
+
);
|
|
97
|
+
if (options.register !== false) {
|
|
98
|
+
this.registry.addInteractionClass(toInteractionClassSchema(schema));
|
|
99
|
+
}
|
|
100
|
+
this._interactionAdapters.set(
|
|
101
|
+
resolvedName,
|
|
102
|
+
adapter as InteractionClassAdapter<Record<string, unknown>>
|
|
103
|
+
);
|
|
104
|
+
this._interactionSchemas.set(
|
|
105
|
+
resolvedName,
|
|
106
|
+
schema as DeclarativeInteractionClass<Record<string, unknown>>
|
|
107
|
+
);
|
|
108
|
+
return adapter;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get objectAdapters(): ReadonlyMap<string, ObjectClassAdapter<Record<string, unknown>>> {
|
|
112
|
+
return this._objectAdapters;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get interactionAdapters(): ReadonlyMap<string, InteractionClassAdapter<Record<string, unknown>>> {
|
|
116
|
+
return this._interactionAdapters;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get objectSchemas(): ReadonlyMap<string, DeclarativeObjectClass<Record<string, unknown>>> {
|
|
120
|
+
return this._objectSchemas;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get interactionSchemas(): ReadonlyMap<string, DeclarativeInteractionClass<Record<string, unknown>>> {
|
|
124
|
+
return this._interactionSchemas;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getObjectAdapter<T extends object>(name: string): ObjectClassAdapter<T> | undefined {
|
|
128
|
+
return this._objectAdapters.get(name) as ObjectClassAdapter<T> | undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getInteractionAdapter<T extends object>(name: string): InteractionClassAdapter<T> | undefined {
|
|
132
|
+
return this._interactionAdapters.get(name) as InteractionClassAdapter<T> | undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getObjectSchema<T extends object>(name: string): DeclarativeObjectClass<T> | undefined {
|
|
136
|
+
return this._objectSchemas.get(name) as DeclarativeObjectClass<T> | undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
getInteractionSchema<T extends object>(
|
|
140
|
+
name: string
|
|
141
|
+
): DeclarativeInteractionClass<T> | undefined {
|
|
142
|
+
return this._interactionSchemas.get(name) as DeclarativeInteractionClass<T> | undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async resolveAll(rti: RTIAmbassador): Promise<void> {
|
|
146
|
+
for (const adapter of this._objectAdapters.values()) {
|
|
147
|
+
await adapter.resolve(rti);
|
|
148
|
+
}
|
|
149
|
+
for (const adapter of this._interactionAdapters.values()) {
|
|
150
|
+
await adapter.resolve(rti);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async publishAll(rti: RTIAmbassador): Promise<void> {
|
|
155
|
+
for (const adapter of this._objectAdapters.values()) {
|
|
156
|
+
await adapter.publish(rti);
|
|
157
|
+
}
|
|
158
|
+
for (const adapter of this._interactionAdapters.values()) {
|
|
159
|
+
await adapter.publish(rti);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async subscribeAll(rti: RTIAmbassador): Promise<void> {
|
|
164
|
+
for (const adapter of this._objectAdapters.values()) {
|
|
165
|
+
await adapter.subscribe(rti);
|
|
166
|
+
}
|
|
167
|
+
for (const adapter of this._interactionAdapters.values()) {
|
|
168
|
+
await adapter.subscribe(rti);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function resolveHierarchicalName(name: string, parent?: string): string {
|
|
174
|
+
if (name.includes(".")) return name;
|
|
175
|
+
if (!parent) return name;
|
|
176
|
+
return `${parent}.${name}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function mapDeclarativeAttributes<T extends object>(
|
|
180
|
+
schema: DeclarativeObjectClass<T>
|
|
181
|
+
): { [K in keyof T]: { name: string; encode: (value: T[K]) => Uint8Array; decode?: (data: Uint8Array) => T[K] } } {
|
|
182
|
+
const entries = Object.entries(schema.attributes) as Array<
|
|
183
|
+
[keyof T, DeclarativeAttribute<T[keyof T]>]
|
|
184
|
+
>;
|
|
185
|
+
const result: Record<string, { name: string; encode: (value: unknown) => Uint8Array; decode?: (data: Uint8Array) => unknown }> = {};
|
|
186
|
+
for (const [key, attribute] of entries) {
|
|
187
|
+
result[key as string] = {
|
|
188
|
+
name: attribute.name,
|
|
189
|
+
encode: attribute.encode as (value: unknown) => Uint8Array,
|
|
190
|
+
decode: attribute.decode as ((data: Uint8Array) => unknown) | undefined,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return result as { [K in keyof T]: { name: string; encode: (value: T[K]) => Uint8Array; decode?: (data: Uint8Array) => T[K] } };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mapDeclarativeParameters<T extends object>(
|
|
197
|
+
schema: DeclarativeInteractionClass<T>
|
|
198
|
+
): { [K in keyof T]: { name: string; encode: (value: T[K]) => Uint8Array; decode?: (data: Uint8Array) => T[K] } } {
|
|
199
|
+
const entries = Object.entries(schema.parameters) as Array<
|
|
200
|
+
[keyof T, DeclarativeParameter<T[keyof T]>]
|
|
201
|
+
>;
|
|
202
|
+
const result: Record<string, { name: string; encode: (value: unknown) => Uint8Array; decode?: (data: Uint8Array) => unknown }> = {};
|
|
203
|
+
for (const [key, parameter] of entries) {
|
|
204
|
+
result[key as string] = {
|
|
205
|
+
name: parameter.name,
|
|
206
|
+
encode: parameter.encode as (value: unknown) => Uint8Array,
|
|
207
|
+
decode: parameter.decode as ((data: Uint8Array) => unknown) | undefined,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return result as { [K in keyof T]: { name: string; encode: (value: T[K]) => Uint8Array; decode?: (data: Uint8Array) => T[K] } };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function toObjectClassSchema<T extends object>(schema: DeclarativeObjectClass<T>): ObjectClassSchema {
|
|
214
|
+
return {
|
|
215
|
+
name: schema.name,
|
|
216
|
+
parent: schema.parent,
|
|
217
|
+
sharing: schema.sharing,
|
|
218
|
+
semantics: schema.semantics,
|
|
219
|
+
attributes: (Object.values(schema.attributes) as DeclarativeAttribute<unknown>[]).map(
|
|
220
|
+
(attribute) => ({
|
|
221
|
+
name: attribute.name,
|
|
222
|
+
dataType: attribute.dataType,
|
|
223
|
+
updateType: attribute.updateType as UpdateType,
|
|
224
|
+
updateCondition: attribute.updateCondition,
|
|
225
|
+
ownership: attribute.ownership as OwnershipType,
|
|
226
|
+
sharing: attribute.sharing,
|
|
227
|
+
transportation: attribute.transportation,
|
|
228
|
+
order: attribute.order,
|
|
229
|
+
semantics: attribute.semantics,
|
|
230
|
+
valueRequired: attribute.valueRequired,
|
|
231
|
+
})
|
|
232
|
+
),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function toInteractionClassSchema<T extends object>(
|
|
237
|
+
schema: DeclarativeInteractionClass<T>
|
|
238
|
+
): InteractionClassSchema {
|
|
239
|
+
return {
|
|
240
|
+
name: schema.name,
|
|
241
|
+
parent: schema.parent,
|
|
242
|
+
sharing: schema.sharing,
|
|
243
|
+
transportation: schema.transportation,
|
|
244
|
+
order: schema.order,
|
|
245
|
+
semantics: schema.semantics,
|
|
246
|
+
parameters: (Object.values(schema.parameters) as DeclarativeParameter<unknown>[]).map(
|
|
247
|
+
(parameter) => ({
|
|
248
|
+
name: parameter.name,
|
|
249
|
+
dataType: parameter.dataType,
|
|
250
|
+
semantics: parameter.semantics,
|
|
251
|
+
})
|
|
252
|
+
),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { buildDeclarativeFomFromDecorators, type FomClassConstructor } from "./decorators.ts";
|
|
2
|
+
import type { DeclarativeFom } from "./declarative.ts";
|
|
3
|
+
|
|
4
|
+
export interface DeclareSpaceFomOptions {
|
|
5
|
+
objectClasses?: FomClassConstructor[];
|
|
6
|
+
interactionClasses?: FomClassConstructor[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function declareSpaceFom(options: DeclareSpaceFomOptions = {}): DeclarativeFom {
|
|
10
|
+
return buildDeclarativeFomFromDecorators({
|
|
11
|
+
objectClasses: options.objectClasses ?? [],
|
|
12
|
+
interactionClasses: options.interactionClasses ?? [],
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
FomAttribute,
|
|
4
|
+
FomInteractionClass,
|
|
5
|
+
FomObjectClass,
|
|
6
|
+
FomParameter,
|
|
7
|
+
buildDeclarativeFomFromDecorators,
|
|
8
|
+
getDecoratedInteractionClassMetadata,
|
|
9
|
+
getDecoratedObjectClassMetadata,
|
|
10
|
+
} from "./decorators.ts";
|
|
11
|
+
|
|
12
|
+
class TestObject {
|
|
13
|
+
name = "";
|
|
14
|
+
count = 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class TestInteraction {
|
|
18
|
+
message = "";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test("decorator metadata produces declarative schemas", () => {
|
|
22
|
+
FomObjectClass({ name: "HLAobjectRoot.TestObject", sharing: "PublishSubscribe" })(
|
|
23
|
+
TestObject
|
|
24
|
+
);
|
|
25
|
+
FomAttribute({
|
|
26
|
+
dataType: "HLAunicodeString",
|
|
27
|
+
updateType: "Static",
|
|
28
|
+
updateCondition: "during initialization",
|
|
29
|
+
ownership: "NoTransfer",
|
|
30
|
+
sharing: "PublishSubscribe",
|
|
31
|
+
transportation: "HLAreliable",
|
|
32
|
+
order: "TimeStamp",
|
|
33
|
+
encode: (value: string) => new TextEncoder().encode(value),
|
|
34
|
+
})(TestObject.prototype, "name");
|
|
35
|
+
FomAttribute({
|
|
36
|
+
dataType: "HLAinteger32BE",
|
|
37
|
+
updateType: "Periodic",
|
|
38
|
+
updateCondition: "when changed",
|
|
39
|
+
ownership: "NoTransfer",
|
|
40
|
+
sharing: "PublishSubscribe",
|
|
41
|
+
transportation: "HLAreliable",
|
|
42
|
+
order: "TimeStamp",
|
|
43
|
+
encode: (value: number) => {
|
|
44
|
+
const buffer = new ArrayBuffer(4);
|
|
45
|
+
const view = new DataView(buffer);
|
|
46
|
+
view.setInt32(0, value, false);
|
|
47
|
+
return new Uint8Array(buffer);
|
|
48
|
+
},
|
|
49
|
+
})(TestObject.prototype, "count");
|
|
50
|
+
|
|
51
|
+
FomInteractionClass({
|
|
52
|
+
name: "HLAinteractionRoot.TestInteraction",
|
|
53
|
+
sharing: "PublishSubscribe",
|
|
54
|
+
})(TestInteraction);
|
|
55
|
+
FomParameter({
|
|
56
|
+
dataType: "HLAunicodeString",
|
|
57
|
+
encode: (value: string) => new TextEncoder().encode(value),
|
|
58
|
+
})(TestInteraction.prototype, "message");
|
|
59
|
+
|
|
60
|
+
const declarative = buildDeclarativeFomFromDecorators({
|
|
61
|
+
objectClasses: [TestObject],
|
|
62
|
+
interactionClasses: [TestInteraction],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const objectSchema = declarative.getObjectSchema<{ name: string; count: number }>(
|
|
66
|
+
"HLAobjectRoot.TestObject"
|
|
67
|
+
);
|
|
68
|
+
expect(objectSchema?.attributes.name?.dataType).toBe("HLAunicodeString");
|
|
69
|
+
expect(objectSchema?.attributes.count?.dataType).toBe("HLAinteger32BE");
|
|
70
|
+
|
|
71
|
+
const interactionSchema = declarative.getInteractionSchema<{ message: string }>(
|
|
72
|
+
"HLAinteractionRoot.TestInteraction"
|
|
73
|
+
);
|
|
74
|
+
expect(interactionSchema?.parameters.message?.dataType).toBe("HLAunicodeString");
|
|
75
|
+
|
|
76
|
+
const objectMeta = getDecoratedObjectClassMetadata(TestObject);
|
|
77
|
+
expect(objectMeta?.name).toBe("HLAobjectRoot.TestObject");
|
|
78
|
+
expect(objectMeta?.attributes?.[0]?.name).toBe("name");
|
|
79
|
+
|
|
80
|
+
const interactionMeta = getDecoratedInteractionClassMetadata(TestInteraction);
|
|
81
|
+
expect(interactionMeta?.name).toBe("HLAinteractionRoot.TestInteraction");
|
|
82
|
+
expect(interactionMeta?.parameters?.[0]?.name).toBe("message");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
class BaseEntity {
|
|
86
|
+
base = "";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class ExtendedEntity extends BaseEntity {
|
|
90
|
+
extra = 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
test("decorator metadata merges inheritance for object classes", () => {
|
|
94
|
+
FomObjectClass({ name: "HLAobjectRoot.BaseEntity", sharing: "PublishSubscribe" })(BaseEntity);
|
|
95
|
+
FomAttribute({
|
|
96
|
+
dataType: "HLAunicodeString",
|
|
97
|
+
updateType: "Static",
|
|
98
|
+
updateCondition: "during initialization",
|
|
99
|
+
ownership: "NoTransfer",
|
|
100
|
+
sharing: "PublishSubscribe",
|
|
101
|
+
transportation: "HLAreliable",
|
|
102
|
+
order: "TimeStamp",
|
|
103
|
+
encode: (value: string) => new TextEncoder().encode(value),
|
|
104
|
+
})(BaseEntity.prototype, "base");
|
|
105
|
+
|
|
106
|
+
FomObjectClass({ name: "HLAobjectRoot.BaseEntity.ExtendedEntity" })(ExtendedEntity);
|
|
107
|
+
FomAttribute({
|
|
108
|
+
dataType: "HLAinteger32BE",
|
|
109
|
+
updateType: "Periodic",
|
|
110
|
+
updateCondition: "when changed",
|
|
111
|
+
ownership: "NoTransfer",
|
|
112
|
+
sharing: "PublishSubscribe",
|
|
113
|
+
transportation: "HLAreliable",
|
|
114
|
+
order: "TimeStamp",
|
|
115
|
+
encode: (value: number) => {
|
|
116
|
+
const buffer = new ArrayBuffer(4);
|
|
117
|
+
const view = new DataView(buffer);
|
|
118
|
+
view.setInt32(0, value, false);
|
|
119
|
+
return new Uint8Array(buffer);
|
|
120
|
+
},
|
|
121
|
+
})(ExtendedEntity.prototype, "extra");
|
|
122
|
+
|
|
123
|
+
const declarative = buildDeclarativeFomFromDecorators({
|
|
124
|
+
objectClasses: [ExtendedEntity],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const schema = declarative.getObjectSchema<{ base: string; extra: number }>(
|
|
128
|
+
"HLAobjectRoot.BaseEntity.ExtendedEntity"
|
|
129
|
+
);
|
|
130
|
+
expect(schema?.parent).toBe("HLAobjectRoot.BaseEntity");
|
|
131
|
+
expect(schema?.attributes.base?.dataType).toBe("HLAunicodeString");
|
|
132
|
+
expect(schema?.attributes.extra?.dataType).toBe("HLAinteger32BE");
|
|
133
|
+
});
|