utilium 1.10.0 → 2.0.0-pre.1

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/src/struct.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { toUint8Array } from './buffer.js';
1
+ import { BufferViewArray } from './buffer.js';
2
2
  import { _debugLog } from './debugging.js';
3
3
  import * as primitive from './internal/primitives.js';
4
4
  import type {
@@ -6,27 +6,15 @@ import type {
6
6
  Instance,
7
7
  InstanceLike,
8
8
  Member,
9
- MemberContext,
10
- MemberInit,
11
9
  Metadata,
12
10
  Options,
13
11
  Size,
14
12
  StaticLike,
15
13
  TypeLike,
16
14
  } from './internal/struct.js';
17
- import {
18
- _polyfill_metadata,
19
- checkInstance,
20
- checkStruct,
21
- initMetadata,
22
- isCustom,
23
- isInstance,
24
- isStatic,
25
- isStruct,
26
- } from './internal/struct.js';
15
+ import { checkStruct, initMetadata, isInstance, isStatic, isStruct } from './internal/struct.js';
27
16
  import { _throw } from './misc.js';
28
- import { capitalize } from './string.js';
29
- import type { ClassLike } from './types.js';
17
+ import { getAllPrototypes } from './objects.js';
30
18
  export * as Struct from './internal/struct.js';
31
19
 
32
20
  /**
@@ -49,43 +37,33 @@ export function sizeof<T extends TypeLike>(type: T | T[]): Size<T> {
49
37
  if (typeof type == 'string') {
50
38
  primitive.checkValid(type);
51
39
 
52
- return (+primitive.normalize(type).match(primitive.regex)![2] / 8) as Size<T>;
40
+ return primitive.types[primitive.normalize(type)].size as Size<T>;
53
41
  }
54
42
 
55
- if (isCustom(type)) return type[Symbol.size] as Size<T>;
43
+ if (primitive.isType(type)) return type.size as Size<T>;
56
44
 
57
45
  checkStruct(type);
58
46
 
59
47
  const constructor = isStatic(type) ? type : type.constructor;
60
- _polyfill_metadata(constructor);
61
- const { struct } = constructor[Symbol.metadata];
62
-
63
- let size = struct.staticSize;
64
-
65
- if (isStatic(type)) return size as Size<T>;
48
+ const { struct, structInit } = constructor[Symbol.metadata];
66
49
 
67
- for (const member of struct.members.values()) {
68
- const value = (type as any)[member.name];
69
-
70
- if (isInstance(value) && value.constructor[Symbol.metadata].struct.isDynamic) {
71
- if (struct.isUnion) size = Math.max(size, sizeof(value));
72
- else size += sizeof(value);
73
- continue;
74
- }
50
+ if (isStatic(type) || !struct.isDynamic) return struct.staticSize as Size<T>;
75
51
 
76
- if (typeof member.length != 'string') continue;
52
+ const last = structInit.members.at(-1)!;
53
+ const length = (type as any)[last.length as keyof typeof type];
54
+ let dynamicSize = 0;
77
55
 
78
- let subSize = 0;
56
+ if (primitive.isType(last.type)) {
57
+ dynamicSize = last.type.size * length;
58
+ } else {
59
+ const value = type[last.name];
79
60
 
80
- for (let i = 0; i < (type as any)[member.length]; i++) {
81
- subSize += sizeof(isStruct(value[i]) ? value[i] : member.type);
61
+ for (let i = 0; i < length; i++) {
62
+ dynamicSize += sizeof(isStruct(value[i]) ? value[i] : last.type);
82
63
  }
83
-
84
- if (struct.isUnion) size = Math.max(size, subSize);
85
- else size += subSize;
86
64
  }
87
65
 
88
- return size as Size<T>;
66
+ return (struct.isUnion ? Math.max(struct.staticSize, dynamicSize) : struct.staticSize + dynamicSize) as Size<T>;
89
67
  }
90
68
 
91
69
  /**
@@ -96,31 +74,14 @@ export function offsetof(type: StaticLike | InstanceLike, memberName: string): n
96
74
 
97
75
  const constructor = isStatic(type) ? type : type.constructor;
98
76
 
99
- _polyfill_metadata(constructor);
100
- const { struct } = constructor[Symbol.metadata];
101
-
102
- if (isStatic(type) || !struct.isDynamic) {
103
- return (
104
- struct.members.get(memberName)?.staticOffset
105
- ?? _throw(new Error('Struct does not have member: ' + memberName))
106
- );
107
- }
108
-
109
- let offset = 0;
110
-
111
- for (const member of struct.members.values()) {
112
- if (member.name == memberName) return offset;
77
+ const member = constructor[Symbol.metadata].struct.members.get(memberName);
113
78
 
114
- const value = (type as any)[member.name];
115
- offset += sizeof(isStruct(value) ? value : member.type);
116
- }
79
+ if (!member) throw new Error('Struct does not have member: ' + memberName);
117
80
 
118
- throw new Error('Struct does not have member: ' + memberName);
81
+ return member.offset;
119
82
  }
120
83
 
121
- /**
122
- * Aligns a number
123
- */
84
+ /** Aligns a number */
124
85
  export function align(value: number, alignment: number): number {
125
86
  return Math.ceil(value / alignment) * alignment;
126
87
  }
@@ -129,68 +90,50 @@ export function align(value: number, alignment: number): number {
129
90
  * Decorates a class as a struct
130
91
  */
131
92
  export function struct(options: Partial<Options> = {}) {
132
- return function _decorateStruct<const T extends StaticLike>(
93
+ return function _decorateStruct<T extends StaticLike>(
133
94
  target: T,
134
- context: ClassDecoratorContext & DecoratorContext
135
- ): T {
136
- const members = new Map<string, Member>();
137
-
138
- let staticSize = 0,
139
- isDynamic = false;
140
-
141
- for (const { name, type, length } of initMetadata(context)) {
142
- if (!primitive.isValid(type) && !isStatic(type)) throw new TypeError('Not a valid type: ' + type);
143
-
144
- if (typeof length == 'string') {
145
- const countedBy = members.get(length);
95
+ context: ClassDecoratorContext<T> & DecoratorContext
96
+ ): void {
97
+ const init = initMetadata(context);
146
98
 
147
- if (!countedBy) throw new Error(`"${length}" is undefined or declared after "${name}"`);
148
-
149
- if (!primitive.isType(countedBy.type))
150
- throw new Error(`"${length}" is not a number and cannot be used to count "${name}"`);
151
- }
152
-
153
- let decl = `${typeof type == 'string' ? type : type.name} ${name}`;
154
-
155
- if (length !== undefined) decl += `[${length}]`;
156
-
157
- members.set(name, {
158
- name,
159
- staticOffset: options.isUnion ? 0 : staticSize,
160
- type: primitive.isValid(type) ? primitive.normalize(type) : type,
161
- length,
162
- decl,
163
- });
99
+ const members = new Map<string, Member>();
164
100
 
165
- const memberSize =
166
- typeof length == 'string' || (isStatic(type) && type[Symbol.metadata].struct.isDynamic)
167
- ? 0
168
- : sizeof(type) * (length || 1);
101
+ for (const member of init.members) {
102
+ if (options.isUnion) member.offset = 0;
169
103
 
170
- isDynamic ||= isStatic(type) ? type[Symbol.metadata].struct.isDynamic : typeof length == 'string';
171
- staticSize = options.isUnion ? Math.max(staticSize, memberSize) : staticSize + memberSize;
172
- staticSize = align(staticSize, options.align || 1);
104
+ _debugLog('define', target.name + '.' + member.name);
173
105
 
174
- _debugLog('define', target.name + '.' + name);
106
+ members.set(member.name, member);
175
107
  }
176
108
 
177
109
  context.metadata.struct = {
178
110
  options,
179
111
  members,
180
- staticSize,
181
- isDynamic,
112
+ staticSize: init.size,
113
+ isDynamic: init.isDynamic,
182
114
  isUnion: options.isUnion ?? false,
183
115
  } satisfies Metadata;
184
-
185
- return target;
186
116
  };
187
117
  }
188
118
 
119
+ export interface MemberOptions {
120
+ bigEndian?: boolean;
121
+ length?: number | string;
122
+ align?: number;
123
+ typeName?: string;
124
+ }
125
+
189
126
  /**
190
127
  * Decorates a class member to be serialized
191
128
  */
192
- export function member(type: primitive.Valid | ClassLike, length?: number | string) {
193
- return function <V>(value: V, context: MemberContext): V {
129
+ export function member<V>(type: primitive.Type | StaticLike, opt: MemberOptions = {}) {
130
+ return function __decorateMember(value: Target<V>, context: Context<V>): Result<V> {
131
+ if (context.kind != 'accessor') throw new Error('Member must be an accessor');
132
+
133
+ const init = initMetadata(context);
134
+
135
+ if (init.isDynamic) throw new Error('Dynamic members must be declared at the end of the struct');
136
+
194
137
  let name = context.name;
195
138
  if (typeof name == 'symbol') {
196
139
  console.warn('Symbol used for struct member name will be coerced to string: ' + name.toString());
@@ -199,199 +142,141 @@ export function member(type: primitive.Valid | ClassLike, length?: number | stri
199
142
 
200
143
  if (!name) throw new ReferenceError('Invalid name for struct member');
201
144
 
202
- initMetadata(context).push({ name, type, length } satisfies MemberInit);
203
- return value;
204
- };
205
- }
145
+ if (!primitive.isType(type) && !isStatic(type)) throw new TypeError('Not a valid type: ' + type.name);
206
146
 
207
- /** Gets the length of a member */
208
- function _memberLength<T extends Metadata>(instance: Instance<T>, member: Member): number {
209
- if (member.length === undefined) return -1;
210
- if (typeof member.length == 'string') return (instance as any)[member.length];
211
- return Number.isSafeInteger(member.length) && member.length >= 0
212
- ? member.length
213
- : _throw(new Error('Array lengths must be natural numbers'));
214
- }
147
+ if (typeof opt.length == 'string') {
148
+ const countedBy = init.members.find(m => m.name == opt.length);
215
149
 
216
- /**
217
- * Serializes a struct into a Uint8Array
218
- */
219
- export function serialize(instance: unknown): Uint8Array {
220
- if (isCustom(instance) && typeof instance[Symbol.serialize] == 'function') return instance[Symbol.serialize]!();
150
+ if (!countedBy) throw new Error(`"${opt.length}" is not declared and cannot be used to count "${name}"`);
221
151
 
222
- checkInstance(instance);
223
- _polyfill_metadata(instance.constructor);
224
- const { options, members } = instance.constructor[Symbol.metadata].struct;
152
+ if (!primitive.isType(countedBy.type))
153
+ throw new Error(`"${opt.length}" is not a number and cannot be used to count "${name}"`);
225
154
 
226
- const size = sizeof(instance);
227
- const buffer = new Uint8Array(size);
228
- const view = new DataView(buffer.buffer);
155
+ init.isDynamic = true;
156
+ }
229
157
 
230
- _debugLog('serialize', instance.constructor.name);
158
+ const size = align(
159
+ sizeof(type) * (typeof opt.length == 'string' ? 0 : (opt.length ?? 1)),
160
+ opt.align ?? sizeof(type)
161
+ );
231
162
 
232
- let offset = 0,
233
- nextOffset = 0;
163
+ const member = {
164
+ name,
165
+ offset: init.size,
166
+ type,
167
+ length: opt.length,
168
+ size,
169
+ decl: `${opt.typeName ?? type.name} ${name}${opt.length !== undefined ? `[${JSON.stringify(opt.length)}]` : ''}`,
170
+ littleEndian: !opt.bigEndian,
171
+ } satisfies Member;
172
+
173
+ init.members.push(member);
174
+
175
+ // Apply after setting `offset`
176
+ init.size += size;
177
+
178
+ return {
179
+ get() {
180
+ return _get(this, member);
181
+ },
182
+ set(value) {
183
+ _set(this, member, value);
184
+ },
185
+ };
186
+ };
187
+ }
234
188
 
235
- // for unions we should write members in ascending last modified order, but we don't have that info.
236
- for (const member of members.values()) {
237
- const length = _memberLength(instance, member);
189
+ /** Gets the length of a member */
190
+ function _memberLength<T extends Metadata>(instance: Instance<T>, length?: number | string): number {
191
+ if (length === undefined) return -1;
192
+ if (typeof length == 'string') return instance[length];
193
+ return Number.isSafeInteger(length) && length >= 0
194
+ ? length
195
+ : _throw(new Error('Array lengths must be natural numbers'));
196
+ }
238
197
 
239
- _debugLog('\t', member.decl);
198
+ function _set(instance: Instance, member: Member, value: any, index?: number) {
199
+ const { name, type, length: rawLength } = member;
200
+ const length = _memberLength(instance, rawLength);
240
201
 
241
- for (let i = 0; i < Math.abs(length); i++) {
242
- let value = length != -1 ? instance[member.name][i] : instance[member.name];
243
- if (typeof value == 'string') {
244
- value = value.charCodeAt(0);
245
- }
202
+ if (!primitive.isType(type)) {
203
+ if (!isInstance(value)) return _debugLog(`Tried to set "${name}" to a non-instance value`);
246
204
 
247
- offset = nextOffset;
248
- nextOffset += isInstance(value) ? sizeof(value) : sizeof(member.type);
205
+ if (length > 0 && typeof index != 'number') {
206
+ for (let i = 0; i < length; i++) _set(instance, member, value[i], i);
207
+ return;
208
+ }
249
209
 
250
- if (!primitive.isType(member.type)) {
251
- buffer.set(value ? serialize(value) : new Uint8Array(sizeof(member.type)), offset);
252
- continue;
253
- }
210
+ if (!Array.from(getAllPrototypes(value.constructor)).some(c => c === type))
211
+ throw new Error(`${value.constructor.name} is not a subtype of ${type.name}`);
254
212
 
255
- const fn = `set${capitalize(member.type)}` as const;
213
+ const offset = instance.byteOffset + member.offset + (index ?? 0) * sizeof(type);
256
214
 
257
- if (fn == 'setInt64') {
258
- view.setBigInt64(offset, BigInt(value), !options.bigEndian);
259
- continue;
260
- }
215
+ // It's already the same value
216
+ if (value.buffer === instance.buffer && value.byteOffset === offset) return;
261
217
 
262
- if (fn == 'setUint64') {
263
- view.setBigUint64(offset, BigInt(value), !options.bigEndian);
264
- continue;
265
- }
218
+ const current = new Uint8Array(instance.buffer, offset, sizeof(value));
266
219
 
267
- if (fn == 'setInt128') {
268
- view.setBigUint64(offset + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
269
- view.setBigInt64(offset + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
270
- continue;
271
- }
220
+ current.set(new Uint8Array(value.buffer, value.byteOffset, sizeof(value)));
272
221
 
273
- if (fn == 'setUint128') {
274
- view.setBigUint64(offset + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
275
- view.setBigUint64(offset + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
276
- continue;
277
- }
222
+ return;
223
+ }
278
224
 
279
- if (fn == 'setFloat128') {
280
- view.setFloat64(offset + (!options.bigEndian ? 0 : 8), Number(value), !options.bigEndian);
281
- view.setBigUint64(offset + (!options.bigEndian ? 8 : 0), BigInt(0), !options.bigEndian);
282
- continue;
283
- }
225
+ const view = new DataView(instance.buffer, instance.byteOffset, instance.byteLength);
284
226
 
285
- view[fn](offset, Number(value), !options.bigEndian);
227
+ if (length > 0 && typeof index != 'number') {
228
+ for (let i = 0; i < length; i++) {
229
+ const offset = member.offset + i * type.size;
230
+ type.set(view, offset, member.littleEndian, value[i]);
286
231
  }
232
+ return;
287
233
  }
288
234
 
289
- return buffer;
290
- }
291
-
292
- /**
293
- * Deserializes a struct from a Uint8Array
294
- */
295
- export function deserialize(instance: unknown, _buffer: ArrayBufferLike | ArrayBufferView) {
296
- const buffer = toUint8Array(_buffer);
297
-
298
- if (isCustom(instance) && typeof instance[Symbol.deserialize] == 'function')
299
- return instance[Symbol.deserialize]!(buffer);
300
-
301
- checkInstance(instance);
302
- _polyfill_metadata(instance.constructor);
303
- const { options, members } = instance.constructor[Symbol.metadata].struct;
304
-
305
- const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
306
-
307
- _debugLog('deserialize', instance.constructor.name);
308
-
309
- let offset = 0,
310
- nextOffset = 0;
235
+ if (typeof value == 'string') value = value.charCodeAt(0);
311
236
 
312
- for (const member of members.values()) {
313
- const length = _memberLength(instance, member);
314
-
315
- _debugLog('\t', member.decl);
316
-
317
- for (let i = 0; i < Math.abs(length); i++) {
318
- let object = length != -1 ? instance[member.name] : instance;
319
- const key = length != -1 ? i : member.name;
320
-
321
- const isNullish = object[key] === null || object[key] === undefined;
322
-
323
- const needsAllocation = isNullish && isStatic(member.type) && member.type[Symbol.metadata].struct.isDynamic;
324
-
325
- offset = nextOffset;
326
- if (!isInstance(object[key]) && !needsAllocation) nextOffset += sizeof(member.type);
327
-
328
- if (typeof instance[member.name] == 'string') {
329
- instance[member.name] =
330
- instance[member.name].slice(0, i)
331
- + String.fromCharCode(view.getUint8(offset))
332
- + instance[member.name].slice(i + 1);
333
- continue;
334
- }
335
-
336
- if (!primitive.isType(member.type)) {
337
- if (needsAllocation && isStatic(member.type)) object[key] ??= new member.type();
338
- else if (isNullish) continue;
339
-
340
- deserialize(object[key], new Uint8Array(buffer.subarray(offset)));
341
- nextOffset += sizeof(object[key]);
342
- continue;
343
- }
344
-
345
- if (length && length != -1) object ||= [];
346
-
347
- const fn = `get${capitalize(member.type)}` as const;
348
- if (fn == 'getInt64') {
349
- object[key] = view.getBigInt64(offset, !options.bigEndian);
350
- continue;
351
- }
237
+ type.set(view, member.offset + (index ?? 0) * type.size, member.littleEndian, value);
238
+ }
352
239
 
353
- if (fn == 'getUint64') {
354
- object[key] = view.getBigUint64(offset, !options.bigEndian);
355
- continue;
356
- }
240
+ function _get(instance: Instance, member: Member, index?: number) {
241
+ const { type, length: rawLength } = member;
242
+ const length = _memberLength(instance, rawLength);
357
243
 
358
- if (fn == 'getInt128') {
359
- object[key] =
360
- (view.getBigInt64(offset + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
361
- | view.getBigUint64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
362
- continue;
363
- }
244
+ if (length > 0 && typeof index != 'number') {
245
+ return new (primitive.isType(type) ? type.array : BufferViewArray(type, sizeof(type)))(
246
+ instance.buffer,
247
+ instance.byteOffset + member.offset,
248
+ length * sizeof(type)
249
+ );
250
+ }
364
251
 
365
- if (fn == 'getUint128') {
366
- object[key] =
367
- (view.getBigUint64(offset + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
368
- | view.getBigUint64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
369
- continue;
370
- }
252
+ const offset = member.offset + (index ?? 0) * sizeof(type);
371
253
 
372
- if (fn == 'getFloat128') {
373
- object[key] = view.getFloat64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
374
- continue;
375
- }
254
+ if (isStatic(type)) return new type(instance.buffer, offset, sizeof(type));
376
255
 
377
- object[key] = view[fn](offset, !options.bigEndian);
378
- }
379
- }
256
+ const view = new DataView(instance.buffer, instance.byteOffset, instance.byteLength);
257
+ return type.get(view, offset, member.littleEndian);
380
258
  }
381
259
 
382
- function _member<T extends primitive.Valid>(type: T) {
383
- function _structMemberDecorator<const V>(length: number | string): (value: V, context: MemberContext) => V;
384
- function _structMemberDecorator<const V>(value: V, context: MemberContext): V;
385
- function _structMemberDecorator<const V>(
386
- valueOrLength: V | number | string,
387
- context?: MemberContext
388
- ): V | ((value: V, context: MemberContext) => V) {
389
- if (typeof valueOrLength == 'number' || typeof valueOrLength == 'string') {
390
- return member(type, valueOrLength as number);
391
- }
392
-
393
- return member(type)(valueOrLength, context!);
260
+ // Decorator utility types
261
+ type Target<V> = ClassAccessorDecoratorTarget<any, V>;
262
+ type Result<V> = ClassAccessorDecoratorResult<any, V>;
263
+ type Context<V> = ClassAccessorDecoratorContext<any, V> & DecoratorContext;
264
+ type Decorator<V> = (value: Target<V>, context: Context<V>) => Result<V>;
265
+
266
+ function _member<T extends primitive.Valid>(typeName: T) {
267
+ const type = primitive.types[primitive.normalize(typeName)];
268
+
269
+ function _structMemberDecorator<V>(length: number | string): Decorator<V>;
270
+ function _structMemberDecorator<V>(value: Target<V>, context: Context<V>): Result<V>;
271
+ function _structMemberDecorator<V>(
272
+ valueOrLength: Target<V> | number | string,
273
+ context?: Context<V>
274
+ ): Decorator<V> | Result<V> {
275
+ return typeof valueOrLength == 'number' || typeof valueOrLength == 'string'
276
+ ? member<V>(type, { length: valueOrLength, typeName })
277
+ : member<V>(type, { typeName })(valueOrLength, context!);
394
278
  }
279
+
395
280
  return _structMemberDecorator;
396
281
  }
397
282