@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.
@@ -0,0 +1,271 @@
1
+ import { expect, test } from "bun:test";
2
+ import type {
3
+ AttributeHandle,
4
+ AttributeHandleSet,
5
+ InteractionClassHandle,
6
+ ObjectClassHandle,
7
+ ParameterHandle,
8
+ RTIAmbassador,
9
+ } from "@hla4ts/hla-api";
10
+ import { DeclarativeFom } from "./declarative.ts";
11
+ import { DeclarativeRuntime } from "./declarative-runtime.ts";
12
+
13
+ const textEncoder = new TextEncoder();
14
+ const textDecoder = new TextDecoder();
15
+
16
+ function encodeString(value: string): Uint8Array {
17
+ return textEncoder.encode(value);
18
+ }
19
+
20
+ function decodeString(value: Uint8Array): string {
21
+ return textDecoder.decode(value);
22
+ }
23
+
24
+ function encodeInt32(value: number): Uint8Array {
25
+ const buffer = new ArrayBuffer(4);
26
+ new DataView(buffer).setInt32(0, value, false);
27
+ return new Uint8Array(buffer);
28
+ }
29
+
30
+ function decodeInt32(value: Uint8Array): number {
31
+ return new DataView(value.buffer, value.byteOffset, value.byteLength).getInt32(0, false);
32
+ }
33
+
34
+ class MockRti implements Partial<RTIAmbassador> {
35
+ private readonly _objectClassHandles = new Map<string, ObjectClassHandle>();
36
+ private readonly _interactionClassHandles = new Map<string, InteractionClassHandle>();
37
+ private readonly _objectClassNamesByHandle = new Map<string, string>();
38
+ private readonly _interactionClassNamesByHandle = new Map<string, string>();
39
+ private readonly _attributeHandles = new Map<string, AttributeHandle>();
40
+ private readonly _parameterHandles = new Map<string, ParameterHandle>();
41
+
42
+ readonly publishedObjectAttributes: Array<{ className: string; attributes: AttributeHandleSet }> = [];
43
+ readonly subscribedObjectAttributes: Array<{ className: string; attributes: AttributeHandleSet }> = [];
44
+ readonly publishedInteractions: string[] = [];
45
+ readonly subscribedInteractions: string[] = [];
46
+
47
+ private _handleKey(handle: Uint8Array): string {
48
+ return Array.from(handle)
49
+ .map((b) => b.toString(16).padStart(2, "0"))
50
+ .join("");
51
+ }
52
+
53
+ private _makeHandle(label: string): Uint8Array {
54
+ return textEncoder.encode(label);
55
+ }
56
+
57
+ async getObjectClassHandle(objectClassName: string): Promise<ObjectClassHandle> {
58
+ const existing = this._objectClassHandles.get(objectClassName);
59
+ if (existing) return existing;
60
+ const handle = this._makeHandle(`class:${objectClassName}`);
61
+ this._objectClassHandles.set(objectClassName, handle);
62
+ this._objectClassNamesByHandle.set(this._handleKey(handle), objectClassName);
63
+ return handle;
64
+ }
65
+
66
+ async getInteractionClassHandle(interactionClassName: string): Promise<InteractionClassHandle> {
67
+ const existing = this._interactionClassHandles.get(interactionClassName);
68
+ if (existing) return existing;
69
+ const handle = this._makeHandle(`interaction:${interactionClassName}`);
70
+ this._interactionClassHandles.set(interactionClassName, handle);
71
+ this._interactionClassNamesByHandle.set(this._handleKey(handle), interactionClassName);
72
+ return handle;
73
+ }
74
+
75
+ async getAttributeHandle(
76
+ objectClass: ObjectClassHandle,
77
+ attributeName: string
78
+ ): Promise<AttributeHandle> {
79
+ const className = this._objectClassNamesByHandle.get(this._handleKey(objectClass));
80
+ if (!className) {
81
+ throw new Error("Unknown object class handle.");
82
+ }
83
+ const key = `${className}:${attributeName}`;
84
+ const existing = this._attributeHandles.get(key);
85
+ if (existing) return existing;
86
+ const handle = this._makeHandle(`attr:${key}`);
87
+ this._attributeHandles.set(key, handle);
88
+ return handle;
89
+ }
90
+
91
+ async getParameterHandle(
92
+ interactionClass: InteractionClassHandle,
93
+ parameterName: string
94
+ ): Promise<ParameterHandle> {
95
+ const className = this._interactionClassNamesByHandle.get(this._handleKey(interactionClass));
96
+ if (!className) {
97
+ throw new Error("Unknown interaction class handle.");
98
+ }
99
+ const key = `${className}:${parameterName}`;
100
+ const existing = this._parameterHandles.get(key);
101
+ if (existing) return existing;
102
+ const handle = this._makeHandle(`param:${key}`);
103
+ this._parameterHandles.set(key, handle);
104
+ return handle;
105
+ }
106
+
107
+ async publishObjectClassAttributes(
108
+ objectClass: ObjectClassHandle,
109
+ attributes: AttributeHandleSet
110
+ ): Promise<void> {
111
+ const name = this._objectClassNamesByHandle.get(this._handleKey(objectClass)) ?? "unknown";
112
+ this.publishedObjectAttributes.push({ className: name, attributes });
113
+ }
114
+
115
+ async subscribeObjectClassAttributes(
116
+ objectClass: ObjectClassHandle,
117
+ attributes: AttributeHandleSet
118
+ ): Promise<void> {
119
+ const name = this._objectClassNamesByHandle.get(this._handleKey(objectClass)) ?? "unknown";
120
+ this.subscribedObjectAttributes.push({ className: name, attributes });
121
+ }
122
+
123
+ async publishInteractionClass(interactionClass: InteractionClassHandle): Promise<void> {
124
+ const name = this._interactionClassNamesByHandle.get(this._handleKey(interactionClass)) ?? "unknown";
125
+ this.publishedInteractions.push(name);
126
+ }
127
+
128
+ async subscribeInteractionClass(interactionClass: InteractionClassHandle): Promise<void> {
129
+ const name = this._interactionClassNamesByHandle.get(this._handleKey(interactionClass)) ?? "unknown";
130
+ this.subscribedInteractions.push(name);
131
+ }
132
+ }
133
+
134
+ test("DeclarativeRuntime wires handlers and pub/sub based on sharing", async () => {
135
+ const declarative = new DeclarativeFom();
136
+ const entityAdapter = declarative.defineObjectClass<{ name: string; value: number; state: string }>(
137
+ {
138
+ name: "HLAobjectRoot.TestEntity",
139
+ sharing: "PublishSubscribe",
140
+ attributes: {
141
+ name: {
142
+ name: "name",
143
+ dataType: "HLAunicodeString",
144
+ updateType: "Static",
145
+ updateCondition: "during initialization",
146
+ ownership: "NoTransfer",
147
+ sharing: "Publish",
148
+ transportation: "HLAreliable",
149
+ order: "TimeStamp",
150
+ encode: encodeString,
151
+ decode: decodeString,
152
+ },
153
+ value: {
154
+ name: "value",
155
+ dataType: "HLAinteger32BE",
156
+ updateType: "Periodic",
157
+ updateCondition: "when changes",
158
+ ownership: "DivestAcquire",
159
+ sharing: "Subscribe",
160
+ transportation: "HLAreliable",
161
+ order: "TimeStamp",
162
+ encode: encodeInt32,
163
+ decode: decodeInt32,
164
+ },
165
+ state: {
166
+ name: "state",
167
+ dataType: "HLAunicodeString",
168
+ updateType: "Conditional",
169
+ updateCondition: "when changes",
170
+ ownership: "DivestAcquire",
171
+ sharing: "PublishSubscribe",
172
+ transportation: "HLAreliable",
173
+ order: "TimeStamp",
174
+ encode: encodeString,
175
+ decode: decodeString,
176
+ },
177
+ },
178
+ }
179
+ );
180
+
181
+ const interactionAdapter = declarative.defineInteractionClass<{ message: string }>({
182
+ name: "HLAinteractionRoot.TestInteraction",
183
+ sharing: "PublishSubscribe",
184
+ transportation: "HLAreliable",
185
+ order: "Receive",
186
+ parameters: {
187
+ message: {
188
+ name: "message",
189
+ dataType: "HLAunicodeString",
190
+ encode: encodeString,
191
+ decode: decodeString,
192
+ },
193
+ },
194
+ });
195
+
196
+ const runtime = new DeclarativeRuntime(declarative);
197
+ const mockRti = new MockRti();
198
+ const rti = mockRti as unknown as RTIAmbassador;
199
+
200
+ let discoveredName: string | undefined;
201
+ let updateValues: Record<string, unknown> | undefined;
202
+ let interactionMessage: string | undefined;
203
+
204
+ runtime.registerObjectHandlers(entityAdapter, {
205
+ discover: ({ instance }) => {
206
+ discoveredName = instance.objectInstanceName;
207
+ },
208
+ update: ({ values }) => {
209
+ updateValues = values as Record<string, unknown>;
210
+ },
211
+ });
212
+
213
+ runtime.registerInteractionHandlers(interactionAdapter, {
214
+ receive: ({ values }) => {
215
+ interactionMessage = values.message as string;
216
+ },
217
+ });
218
+
219
+ await runtime.prepare(rti);
220
+
221
+ expect(mockRti.publishedObjectAttributes).toHaveLength(1);
222
+ expect(mockRti.subscribedObjectAttributes).toHaveLength(1);
223
+ expect(mockRti.publishedInteractions).toEqual(["HLAinteractionRoot.TestInteraction"]);
224
+ expect(mockRti.subscribedInteractions).toEqual(["HLAinteractionRoot.TestInteraction"]);
225
+
226
+ const publishedHandles = mockRti.publishedObjectAttributes[0]?.attributes ?? [];
227
+ const subscribedHandles = mockRti.subscribedObjectAttributes[0]?.attributes ?? [];
228
+
229
+ const nameHandle = entityAdapter.getAttributeHandle("name");
230
+ const valueHandle = entityAdapter.getAttributeHandle("value");
231
+ const stateHandle = entityAdapter.getAttributeHandle("state");
232
+
233
+ expect(publishedHandles).toContainEqual(nameHandle);
234
+ expect(publishedHandles).toContainEqual(stateHandle);
235
+ expect(publishedHandles).not.toContainEqual(valueHandle);
236
+ expect(subscribedHandles).toContainEqual(valueHandle);
237
+ expect(subscribedHandles).toContainEqual(stateHandle);
238
+ expect(subscribedHandles).not.toContainEqual(nameHandle);
239
+
240
+ const objectInstance = new Uint8Array([1]);
241
+ runtime.handlers.discoverObjectInstance?.(
242
+ objectInstance,
243
+ entityAdapter.classHandle,
244
+ "TestEntity-1",
245
+ new Uint8Array([9])
246
+ );
247
+
248
+ expect(discoveredName).toBe("TestEntity-1");
249
+
250
+ const updates = entityAdapter.encode({ name: "Alpha", value: 42, state: "OK" });
251
+ runtime.handlers.reflectAttributeValues?.(
252
+ objectInstance,
253
+ updates,
254
+ new Uint8Array(),
255
+ new Uint8Array(),
256
+ new Uint8Array([9])
257
+ );
258
+
259
+ expect(updateValues).toEqual({ name: "Alpha", value: 42, state: "OK" });
260
+
261
+ const interactionValues = interactionAdapter.encode({ message: "Hello" });
262
+ runtime.handlers.receiveInteraction?.(
263
+ interactionAdapter.interactionHandle,
264
+ interactionValues,
265
+ new Uint8Array(),
266
+ new Uint8Array(),
267
+ new Uint8Array([9])
268
+ );
269
+
270
+ expect(interactionMessage).toBe("Hello");
271
+ });