node-ctypes 0.1.5 → 1.0.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.
Potentially problematic release.
This version of node-ctypes might be problematic. Click here for more details.
- package/README.md +14 -9
- package/build/Release/ctypes-darwin-arm64.node +0 -0
- package/build/Release/ctypes-darwin-x64.node +0 -0
- package/build/Release/ctypes-linux-arm64.node +0 -0
- package/build/Release/ctypes-linux-x64.node +0 -0
- package/build/Release/ctypes-win32-arm64.node +0 -0
- package/build/Release/ctypes-win32-x64.node +0 -0
- package/lib/core/Library.js +312 -0
- package/lib/core/callback.js +275 -0
- package/lib/core/types.js +140 -0
- package/lib/index.d.ts +71 -124
- package/lib/index.js +215 -3096
- package/lib/memory/buffer.js +404 -0
- package/lib/memory/operations.js +295 -0
- package/lib/memory/pointer.js +358 -0
- package/lib/platform/constants.js +110 -0
- package/lib/platform/errors.js +403 -0
- package/lib/structures/Structure.js +414 -0
- package/lib/structures/Union.js +102 -0
- package/lib/structures/helpers/array.js +261 -0
- package/lib/structures/helpers/bitfield.js +147 -0
- package/lib/structures/helpers/common.js +129 -0
- package/lib/structures/helpers/struct.js +925 -0
- package/lib/structures/helpers/union.js +465 -0
- package/lib/types/SimpleCData.js +193 -0
- package/lib/types/primitives.js +392 -0
- package/lib/utils/cache.js +142 -0
- package/package.json +1 -1
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file struct.js
|
|
3
|
+
* @module structures/struct
|
|
4
|
+
* @description Struct type definition implementation for C-style structures.
|
|
5
|
+
*
|
|
6
|
+
* This module provides Python ctypes-compatible struct types with proper
|
|
7
|
+
* alignment, padding, nested structures, arrays, and bitfields support.
|
|
8
|
+
*
|
|
9
|
+
* **Python ctypes Compatibility**:
|
|
10
|
+
* Similar to Python's `ctypes.Structure` class with `_fields_` definition.
|
|
11
|
+
*
|
|
12
|
+
* @example Basic struct
|
|
13
|
+
* ```javascript
|
|
14
|
+
* import { struct, c_int32, c_float } from 'node-ctypes';
|
|
15
|
+
*
|
|
16
|
+
* const Point = struct({
|
|
17
|
+
* x: c_int32,
|
|
18
|
+
* y: c_int32
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* const point = Point.create({ x: 10, y: 20 });
|
|
22
|
+
* console.log(point.x, point.y); // 10 20
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Struct with nested types and bitfields
|
|
26
|
+
* ```javascript
|
|
27
|
+
* const Header = struct({
|
|
28
|
+
* magic: c_uint32,
|
|
29
|
+
* flags: bitfield(c_uint32, 8),
|
|
30
|
+
* version: bitfield(c_uint32, 8),
|
|
31
|
+
* reserved: bitfield(c_uint32, 16)
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { _readBitField, _writeBitField } from "./bitfield.js";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a struct type definition with proper alignment and padding.
|
|
40
|
+
*
|
|
41
|
+
* Returns a struct definition object with methods to create instances,
|
|
42
|
+
* read/write fields, and convert to/from JavaScript objects. Handles
|
|
43
|
+
* alignment, padding, nested structures, arrays, and bitfields.
|
|
44
|
+
*
|
|
45
|
+
* **Python ctypes Compatibility**:
|
|
46
|
+
* Similar to Python's `Structure` class with `_fields_` attribute.
|
|
47
|
+
*
|
|
48
|
+
* @param {Object} fields - Field definitions { name: type, ... }
|
|
49
|
+
* @param {Object} [options] - Options { packed: false }
|
|
50
|
+
* @param {boolean} [options.packed=false] - If true, disable padding/alignment
|
|
51
|
+
* @param {Function} sizeof - sizeof function reference
|
|
52
|
+
* @param {Function} alloc - alloc function reference
|
|
53
|
+
* @param {Function} readValue - readValue function reference
|
|
54
|
+
* @param {Function} writeValue - writeValue function reference
|
|
55
|
+
* @param {Function} _isStruct - _isStruct function reference
|
|
56
|
+
* @param {Function} _isArrayType - _isArrayType function reference
|
|
57
|
+
* @param {Function} _isBitField - _isBitField function reference
|
|
58
|
+
* @param {Function} Structure - Structure class reference
|
|
59
|
+
* @param {Function} Union - Union class reference
|
|
60
|
+
* @param {Object} native - Native module reference
|
|
61
|
+
* @returns {Object} Struct definition with create, get, set, toObject methods
|
|
62
|
+
* @throws {TypeError} If fields are invalid or field names are duplicated
|
|
63
|
+
*
|
|
64
|
+
* @example Create struct with alignment
|
|
65
|
+
* ```javascript
|
|
66
|
+
* const Data = struct({
|
|
67
|
+
* a: c_uint8, // offset 0, size 1
|
|
68
|
+
* // padding: 3 bytes for alignment
|
|
69
|
+
* b: c_uint32, // offset 4, size 4
|
|
70
|
+
* c: c_uint16 // offset 8, size 2
|
|
71
|
+
* });
|
|
72
|
+
* console.log(Data.size); // 12 (with padding)
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @example Packed struct (no padding)
|
|
76
|
+
* ```javascript
|
|
77
|
+
* const PackedData = struct({
|
|
78
|
+
* a: c_uint8, // offset 0
|
|
79
|
+
* b: c_uint32, // offset 1
|
|
80
|
+
* c: c_uint16 // offset 5
|
|
81
|
+
* }, { packed: true });
|
|
82
|
+
* console.log(PackedData.size); // 7 (no padding)
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function struct(fields, options = {}, sizeof, alloc, readValue, writeValue, _isStruct, _isArrayType, _isBitField, Structure, Union, native) {
|
|
86
|
+
// Validate inputs
|
|
87
|
+
if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
|
|
88
|
+
throw new TypeError("fields must be a non-null object");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const packed = options.packed || false;
|
|
92
|
+
let totalSize = 0;
|
|
93
|
+
const fieldDefs = [];
|
|
94
|
+
let maxAlignment = 1;
|
|
95
|
+
|
|
96
|
+
// Track field names to detect duplicates
|
|
97
|
+
const fieldNames = new Set();
|
|
98
|
+
|
|
99
|
+
// Stato per bit fields consecutivi
|
|
100
|
+
let currentBitFieldBase = null;
|
|
101
|
+
let currentBitFieldOffset = 0;
|
|
102
|
+
let currentBitOffset = 0;
|
|
103
|
+
|
|
104
|
+
for (const [name, type] of Object.entries(fields)) {
|
|
105
|
+
// Validate field name
|
|
106
|
+
if (!name || typeof name !== "string" || name.trim().length === 0) {
|
|
107
|
+
throw new TypeError("Field name must be a non-empty string");
|
|
108
|
+
}
|
|
109
|
+
if (fieldNames.has(name)) {
|
|
110
|
+
throw new TypeError(`Duplicate field name: ${name}`);
|
|
111
|
+
}
|
|
112
|
+
fieldNames.add(name);
|
|
113
|
+
let fieldDef;
|
|
114
|
+
|
|
115
|
+
// Caso 0: Anonymous field - { type: SomeStruct, anonymous: true }
|
|
116
|
+
if (typeof type === "object" && type !== null && type.anonymous === true && type.type) {
|
|
117
|
+
// Reset bit field state
|
|
118
|
+
currentBitFieldBase = null;
|
|
119
|
+
currentBitOffset = 0;
|
|
120
|
+
|
|
121
|
+
const actualType = type.type;
|
|
122
|
+
const size = actualType.size;
|
|
123
|
+
const alignment = packed ? 1 : actualType.alignment;
|
|
124
|
+
|
|
125
|
+
// Applica padding per allineamento
|
|
126
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
127
|
+
totalSize += alignment - (totalSize % alignment);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fieldDef = {
|
|
131
|
+
name,
|
|
132
|
+
type: actualType,
|
|
133
|
+
offset: totalSize,
|
|
134
|
+
size,
|
|
135
|
+
alignment,
|
|
136
|
+
isNested: true,
|
|
137
|
+
isAnonymous: true,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
totalSize += size;
|
|
141
|
+
|
|
142
|
+
if (alignment > maxAlignment) {
|
|
143
|
+
maxAlignment = alignment;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Caso 1: Bit field
|
|
147
|
+
else if (_isBitField(type)) {
|
|
148
|
+
const { baseType, bits, baseSize } = type;
|
|
149
|
+
const alignment = packed ? 1 : Math.min(baseSize, native.POINTER_SIZE);
|
|
150
|
+
|
|
151
|
+
// Verifica se possiamo continuare nel bit field corrente
|
|
152
|
+
const canContinue = currentBitFieldBase === baseType && currentBitOffset + bits <= baseSize * 8;
|
|
153
|
+
|
|
154
|
+
if (!canContinue) {
|
|
155
|
+
// Inizia un nuovo bit field
|
|
156
|
+
// Applica padding per allineamento
|
|
157
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
158
|
+
totalSize += alignment - (totalSize % alignment);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
currentBitFieldBase = baseType;
|
|
162
|
+
currentBitFieldOffset = totalSize;
|
|
163
|
+
currentBitOffset = 0;
|
|
164
|
+
totalSize += baseSize;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
fieldDef = {
|
|
168
|
+
name,
|
|
169
|
+
type: baseType,
|
|
170
|
+
offset: currentBitFieldOffset,
|
|
171
|
+
size: 0, // Bit fields non aggiungono size extra
|
|
172
|
+
alignment,
|
|
173
|
+
isBitField: true,
|
|
174
|
+
bitOffset: currentBitOffset,
|
|
175
|
+
bitSize: bits,
|
|
176
|
+
baseSize,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
currentBitOffset += bits;
|
|
180
|
+
|
|
181
|
+
if (alignment > maxAlignment) {
|
|
182
|
+
maxAlignment = alignment;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Caso 2a: Struct class (declarata come `class X extends Structure`)
|
|
186
|
+
else if (typeof type === "function" && type.prototype instanceof Structure) {
|
|
187
|
+
// Reset bit field state
|
|
188
|
+
currentBitFieldBase = null;
|
|
189
|
+
currentBitOffset = 0;
|
|
190
|
+
|
|
191
|
+
const nestedStruct = type._structDef || type._buildStruct();
|
|
192
|
+
const size = nestedStruct.size;
|
|
193
|
+
const alignment = packed ? 1 : nestedStruct.alignment;
|
|
194
|
+
|
|
195
|
+
// Applica padding per allineamento
|
|
196
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
197
|
+
totalSize += alignment - (totalSize % alignment);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fieldDef = {
|
|
201
|
+
name,
|
|
202
|
+
type: nestedStruct,
|
|
203
|
+
offset: totalSize,
|
|
204
|
+
size,
|
|
205
|
+
alignment,
|
|
206
|
+
isNested: true,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
totalSize += size;
|
|
210
|
+
|
|
211
|
+
if (alignment > maxAlignment) {
|
|
212
|
+
maxAlignment = alignment;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Caso 2b: Union class (declarata come `class X extends Union`)
|
|
216
|
+
else if (typeof type === "function" && type.prototype instanceof Union) {
|
|
217
|
+
// Reset bit field state
|
|
218
|
+
currentBitFieldBase = null;
|
|
219
|
+
currentBitOffset = 0;
|
|
220
|
+
|
|
221
|
+
const nestedUnion = type._unionDef || type._buildUnion();
|
|
222
|
+
const size = nestedUnion.size;
|
|
223
|
+
const alignment = packed ? 1 : nestedUnion.alignment;
|
|
224
|
+
|
|
225
|
+
// Applica padding per allineamento
|
|
226
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
227
|
+
totalSize += alignment - (totalSize % alignment);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fieldDef = {
|
|
231
|
+
name,
|
|
232
|
+
type: nestedUnion,
|
|
233
|
+
offset: totalSize,
|
|
234
|
+
size,
|
|
235
|
+
alignment,
|
|
236
|
+
isNested: true,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
totalSize += size;
|
|
240
|
+
|
|
241
|
+
if (alignment > maxAlignment) {
|
|
242
|
+
maxAlignment = alignment;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Caso 2: Nested struct
|
|
246
|
+
else if (_isStruct(type)) {
|
|
247
|
+
// Reset bit field state
|
|
248
|
+
currentBitFieldBase = null;
|
|
249
|
+
currentBitOffset = 0;
|
|
250
|
+
|
|
251
|
+
const nestedStruct = type;
|
|
252
|
+
const size = nestedStruct.size;
|
|
253
|
+
const alignment = packed ? 1 : nestedStruct.alignment;
|
|
254
|
+
|
|
255
|
+
// Applica padding per allineamento
|
|
256
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
257
|
+
totalSize += alignment - (totalSize % alignment);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
fieldDef = {
|
|
261
|
+
name,
|
|
262
|
+
type: nestedStruct,
|
|
263
|
+
offset: totalSize,
|
|
264
|
+
size,
|
|
265
|
+
alignment,
|
|
266
|
+
isNested: true,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
totalSize += size;
|
|
270
|
+
|
|
271
|
+
if (alignment > maxAlignment) {
|
|
272
|
+
maxAlignment = alignment;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Caso 3: Array type
|
|
276
|
+
else if (_isArrayType(type)) {
|
|
277
|
+
// Reset bit field state
|
|
278
|
+
currentBitFieldBase = null;
|
|
279
|
+
currentBitOffset = 0;
|
|
280
|
+
|
|
281
|
+
const size = type.getSize();
|
|
282
|
+
const elemSize = sizeof(type.elementType);
|
|
283
|
+
const alignment = packed ? 1 : Math.min(elemSize, native.POINTER_SIZE);
|
|
284
|
+
|
|
285
|
+
// Applica padding per allineamento
|
|
286
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
287
|
+
totalSize += alignment - (totalSize % alignment);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fieldDef = {
|
|
291
|
+
name,
|
|
292
|
+
type,
|
|
293
|
+
offset: totalSize,
|
|
294
|
+
size,
|
|
295
|
+
alignment,
|
|
296
|
+
isArray: true,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
totalSize += size;
|
|
300
|
+
|
|
301
|
+
if (alignment > maxAlignment) {
|
|
302
|
+
maxAlignment = alignment;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Caso 4: Tipo base (SimpleCData)
|
|
306
|
+
else {
|
|
307
|
+
// Reset bit field state
|
|
308
|
+
currentBitFieldBase = null;
|
|
309
|
+
currentBitOffset = 0;
|
|
310
|
+
|
|
311
|
+
// Validate type is a SimpleCData class
|
|
312
|
+
if (!(typeof type === "function" && type._isSimpleCData)) {
|
|
313
|
+
throw new TypeError(`struct field "${name}": type must be a SimpleCData class, struct, union, or array`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const size = type._size;
|
|
317
|
+
const naturalAlign = Math.min(size, native.POINTER_SIZE);
|
|
318
|
+
const alignment = packed ? 1 : naturalAlign;
|
|
319
|
+
|
|
320
|
+
// Applica padding per allineamento
|
|
321
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
322
|
+
totalSize += alignment - (totalSize % alignment);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
fieldDef = {
|
|
326
|
+
name,
|
|
327
|
+
type,
|
|
328
|
+
offset: totalSize,
|
|
329
|
+
size,
|
|
330
|
+
alignment,
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
totalSize += size;
|
|
334
|
+
|
|
335
|
+
if (alignment > maxAlignment) {
|
|
336
|
+
maxAlignment = alignment;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
fieldDefs.push(fieldDef);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Padding finale per allineamento della struct
|
|
344
|
+
if (!packed && totalSize % maxAlignment !== 0) {
|
|
345
|
+
totalSize += maxAlignment - (totalSize % maxAlignment);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Crea Map per lookup O(1) invece di find() O(n)
|
|
349
|
+
const fieldMap = new Map();
|
|
350
|
+
for (const field of fieldDefs) {
|
|
351
|
+
fieldMap.set(field.name, field);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Pre-compile reader/writer functions for each field
|
|
355
|
+
const fieldReaders = new Map();
|
|
356
|
+
const fieldWriters = new Map();
|
|
357
|
+
|
|
358
|
+
for (const field of fieldDefs) {
|
|
359
|
+
const offset = field.offset;
|
|
360
|
+
const name = field.name;
|
|
361
|
+
|
|
362
|
+
// Skip complex types - they need special handling
|
|
363
|
+
if (field.isBitField || field.isNested || field.isArray) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Pre-compile based on SimpleCData type
|
|
368
|
+
// Use the type's _reader and _writer directly for optimization
|
|
369
|
+
const fieldType = field.type;
|
|
370
|
+
if (fieldType && fieldType._isSimpleCData) {
|
|
371
|
+
const fieldOffset = offset; // Capture offset for closure
|
|
372
|
+
fieldReaders.set(name, (buf) => fieldType._reader(buf, fieldOffset));
|
|
373
|
+
fieldWriters.set(name, (buf, val) => fieldType._writer(buf, fieldOffset, val));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const structDef = {
|
|
378
|
+
size: totalSize,
|
|
379
|
+
alignment: maxAlignment,
|
|
380
|
+
fields: fieldDefs,
|
|
381
|
+
packed: packed,
|
|
382
|
+
_isStructType: true,
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Alloca e inizializza una nuova istanza
|
|
386
|
+
* @param {Object} [values] - Valori iniziali
|
|
387
|
+
* @returns {Proxy} Proxy-based struct instance
|
|
388
|
+
*/
|
|
389
|
+
create(values = {}) {
|
|
390
|
+
const buf = alloc(totalSize);
|
|
391
|
+
buf.fill(0); // Inizializza a zero
|
|
392
|
+
|
|
393
|
+
// Prima imposta i campi diretti
|
|
394
|
+
for (const field of fieldDefs) {
|
|
395
|
+
if (values[field.name] !== undefined) {
|
|
396
|
+
structDef.set(buf, field.name, values[field.name]);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Poi gestisci i campi anonymous - i loro campi possono essere passati direttamente nell'oggetto values
|
|
401
|
+
for (const field of fieldDefs) {
|
|
402
|
+
if (field.isAnonymous && field.type && field.type.fields) {
|
|
403
|
+
for (const subField of field.type.fields) {
|
|
404
|
+
if (values[subField.name] !== undefined) {
|
|
405
|
+
structDef.set(buf, subField.name, values[subField.name]);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Proxy-based instance with pre-compiled readers/writers
|
|
412
|
+
return new Proxy(buf, {
|
|
413
|
+
get(target, prop, receiver) {
|
|
414
|
+
// Special properties
|
|
415
|
+
if (prop === "_buffer") return target;
|
|
416
|
+
if (prop === "toObject") return () => structDef.toObject(target);
|
|
417
|
+
if (prop === Symbol.toStringTag) return "StructInstance";
|
|
418
|
+
if (prop === Symbol.iterator) return undefined;
|
|
419
|
+
|
|
420
|
+
// Handle Buffer/TypedArray properties FIRST before field checks
|
|
421
|
+
// TypedArray methods need direct access to avoid Proxy interference
|
|
422
|
+
if (prop === "length" || prop === "byteLength" || prop === "byteOffset" || prop === "buffer" || prop === "BYTES_PER_ELEMENT") {
|
|
423
|
+
return target[prop];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Use pre-compiled reader if available
|
|
427
|
+
if (typeof prop === "string") {
|
|
428
|
+
const reader = fieldReaders.get(prop);
|
|
429
|
+
if (reader) return reader(target);
|
|
430
|
+
|
|
431
|
+
// Fallback to general get for complex types
|
|
432
|
+
if (fieldMap.has(prop)) {
|
|
433
|
+
return structDef.get(target, prop);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Check anonymous fields
|
|
438
|
+
for (const f of fieldDefs) {
|
|
439
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
440
|
+
if (f.type.fields.some((sf) => sf.name === prop)) {
|
|
441
|
+
return structDef.get(target, prop);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Fallback to buffer properties and methods
|
|
447
|
+
const value = target[prop];
|
|
448
|
+
if (typeof value === "function") {
|
|
449
|
+
return value.bind(target);
|
|
450
|
+
}
|
|
451
|
+
return value;
|
|
452
|
+
},
|
|
453
|
+
set(target, prop, value, receiver) {
|
|
454
|
+
// FAST PATH: Use pre-compiled writer if available
|
|
455
|
+
if (typeof prop === "string") {
|
|
456
|
+
const writer = fieldWriters.get(prop);
|
|
457
|
+
if (writer) {
|
|
458
|
+
writer(target, value);
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Fallback to general set for complex types
|
|
463
|
+
if (fieldMap.has(prop)) {
|
|
464
|
+
structDef.set(target, prop, value);
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Check anonymous fields
|
|
470
|
+
for (const f of fieldDefs) {
|
|
471
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
472
|
+
if (f.type.fields.some((sf) => sf.name === prop)) {
|
|
473
|
+
structDef.set(target, prop, value);
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Fallback
|
|
480
|
+
return Reflect.set(target, prop, value, target);
|
|
481
|
+
},
|
|
482
|
+
has(target, prop) {
|
|
483
|
+
if (prop === "_buffer" || prop === "toObject") return true;
|
|
484
|
+
if (typeof prop === "string" && fieldMap.has(prop)) return true;
|
|
485
|
+
// Check anonymous fields
|
|
486
|
+
for (const f of fieldDefs) {
|
|
487
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
488
|
+
if (f.type.fields.some((sf) => sf.name === prop)) return true;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return Reflect.has(target, prop);
|
|
492
|
+
},
|
|
493
|
+
ownKeys(target) {
|
|
494
|
+
const keys = [];
|
|
495
|
+
for (const f of fieldDefs) {
|
|
496
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
497
|
+
for (const sf of f.type.fields) {
|
|
498
|
+
keys.push(sf.name);
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
keys.push(f.name);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return keys;
|
|
505
|
+
},
|
|
506
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
507
|
+
if (typeof prop === "string" && (fieldMap.has(prop) || prop === "_buffer" || prop === "toObject")) {
|
|
508
|
+
return {
|
|
509
|
+
enumerable: prop !== "_buffer" && prop !== "toObject",
|
|
510
|
+
configurable: true,
|
|
511
|
+
writable: true,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
// Check anonymous fields
|
|
515
|
+
for (const f of fieldDefs) {
|
|
516
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
517
|
+
if (f.type.fields.some((sf) => sf.name === prop)) {
|
|
518
|
+
return {
|
|
519
|
+
enumerable: true,
|
|
520
|
+
configurable: true,
|
|
521
|
+
writable: true,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Legge un campo dalla struttura
|
|
533
|
+
* @param {Buffer|Object} bufOrObj - Buffer della struttura O object wrapper
|
|
534
|
+
* @param {string} fieldName - Nome del campo (supporta 'outer.inner' per nested)
|
|
535
|
+
* @returns {*} Valore del campo
|
|
536
|
+
*/
|
|
537
|
+
get(bufOrObj, fieldName) {
|
|
538
|
+
// supporta sia buffer che object wrapper o plain object
|
|
539
|
+
let buf;
|
|
540
|
+
if (Buffer.isBuffer(bufOrObj)) {
|
|
541
|
+
buf = bufOrObj;
|
|
542
|
+
} else if (bufOrObj && bufOrObj._buffer) {
|
|
543
|
+
// Object wrapper - gestisci dot notation
|
|
544
|
+
if (fieldName.includes(".")) {
|
|
545
|
+
const parts = fieldName.split(".");
|
|
546
|
+
let current = bufOrObj;
|
|
547
|
+
for (const part of parts) {
|
|
548
|
+
current = current[part];
|
|
549
|
+
if (current === undefined) return undefined;
|
|
550
|
+
}
|
|
551
|
+
return current;
|
|
552
|
+
}
|
|
553
|
+
// Accesso diretto tramite property
|
|
554
|
+
return bufOrObj[fieldName];
|
|
555
|
+
} else if (typeof bufOrObj === "object" && bufOrObj !== null) {
|
|
556
|
+
// Plain object (da toObject/create) - accesso diretto
|
|
557
|
+
if (fieldName.includes(".")) {
|
|
558
|
+
const parts = fieldName.split(".");
|
|
559
|
+
let current = bufOrObj;
|
|
560
|
+
for (const part of parts) {
|
|
561
|
+
current = current[part];
|
|
562
|
+
if (current === undefined) return undefined;
|
|
563
|
+
}
|
|
564
|
+
return current;
|
|
565
|
+
}
|
|
566
|
+
// Accesso diretto tramite property
|
|
567
|
+
return bufOrObj[fieldName];
|
|
568
|
+
} else {
|
|
569
|
+
throw new TypeError("Expected Buffer or struct instance");
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Fast path: nome semplice senza dot notation
|
|
573
|
+
// Lookup with Map
|
|
574
|
+
let field = fieldMap.get(fieldName);
|
|
575
|
+
|
|
576
|
+
if (field) {
|
|
577
|
+
// Bit field
|
|
578
|
+
if (field.isBitField) {
|
|
579
|
+
return _readBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, sizeof);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Nested struct (senza dot = ritorna intero oggetto)
|
|
583
|
+
if (field.isNested) {
|
|
584
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
585
|
+
// Proxy-based nested instance
|
|
586
|
+
return field.type.create
|
|
587
|
+
? // Se ha create(), usa quello per creare il proxy
|
|
588
|
+
new Proxy(nestedBuf, {
|
|
589
|
+
get(target, prop, receiver) {
|
|
590
|
+
if (prop === "_buffer") return target;
|
|
591
|
+
if (prop === "toObject") return () => field.type.toObject(target);
|
|
592
|
+
if (prop === Symbol.toStringTag) return "NestedStructInstance";
|
|
593
|
+
if (prop === Symbol.iterator) return undefined;
|
|
594
|
+
// Handle Buffer/TypedArray properties
|
|
595
|
+
if (prop === "length" || prop === "byteLength" || prop === "byteOffset" || prop === "buffer" || prop === "BYTES_PER_ELEMENT") {
|
|
596
|
+
return target[prop];
|
|
597
|
+
}
|
|
598
|
+
if (typeof prop === "string") {
|
|
599
|
+
// Check direct fields
|
|
600
|
+
const nestedFieldMap = field.type.fields ? new Map(field.type.fields.map((f) => [f.name, f])) : new Map();
|
|
601
|
+
if (nestedFieldMap.has(prop)) {
|
|
602
|
+
return field.type.get(target, prop);
|
|
603
|
+
}
|
|
604
|
+
// Check anonymous fields
|
|
605
|
+
for (const sf of field.type.fields || []) {
|
|
606
|
+
if (sf.isAnonymous && sf.type && sf.type.fields) {
|
|
607
|
+
if (sf.type.fields.some((ssf) => ssf.name === prop)) {
|
|
608
|
+
return field.type.get(target, prop);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const value = target[prop];
|
|
614
|
+
if (typeof value === "function") return value.bind(target);
|
|
615
|
+
return value;
|
|
616
|
+
},
|
|
617
|
+
set(target, prop, value, receiver) {
|
|
618
|
+
if (typeof prop === "string") {
|
|
619
|
+
const nestedFieldMap = field.type.fields ? new Map(field.type.fields.map((f) => [f.name, f])) : new Map();
|
|
620
|
+
if (nestedFieldMap.has(prop)) {
|
|
621
|
+
field.type.set(target, prop, value);
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
// Check anonymous fields
|
|
625
|
+
for (const sf of field.type.fields || []) {
|
|
626
|
+
if (sf.isAnonymous && sf.type && sf.type.fields) {
|
|
627
|
+
if (sf.type.fields.some((ssf) => ssf.name === prop)) {
|
|
628
|
+
field.type.set(target, prop, value);
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return Reflect.set(target, prop, value, receiver);
|
|
635
|
+
},
|
|
636
|
+
has(target, prop) {
|
|
637
|
+
if (prop === "_buffer" || prop === "toObject") return true;
|
|
638
|
+
if (typeof prop === "string" && field.type.fields) {
|
|
639
|
+
if (field.type.fields.some((f) => f.name === prop)) return true;
|
|
640
|
+
}
|
|
641
|
+
return Reflect.has(target, prop);
|
|
642
|
+
},
|
|
643
|
+
ownKeys(target) {
|
|
644
|
+
return (field.type.fields || []).map((f) => f.name);
|
|
645
|
+
},
|
|
646
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
647
|
+
if (typeof prop === "string" && field.type.fields && field.type.fields.some((f) => f.name === prop)) {
|
|
648
|
+
return {
|
|
649
|
+
enumerable: true,
|
|
650
|
+
configurable: true,
|
|
651
|
+
writable: true,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
655
|
+
},
|
|
656
|
+
})
|
|
657
|
+
: field.type.toObject(nestedBuf);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Array field
|
|
661
|
+
if (field.isArray) {
|
|
662
|
+
const arrayBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
663
|
+
return field.type.wrap(arrayBuf);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Tipo base - fast path!
|
|
667
|
+
return readValue(buf, field.type, field.offset);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// supporto per accesso nested 'outer.inner' o anonymous fields
|
|
671
|
+
const parts = fieldName.split(".");
|
|
672
|
+
const firstPart = parts[0];
|
|
673
|
+
|
|
674
|
+
field = fieldMap.get(firstPart);
|
|
675
|
+
|
|
676
|
+
// Se non trovato, cerca negli anonymous fields
|
|
677
|
+
if (!field) {
|
|
678
|
+
for (const f of fieldDefs) {
|
|
679
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
680
|
+
const hasField = f.type.fields.some((subField) => subField.name === firstPart);
|
|
681
|
+
if (hasField) {
|
|
682
|
+
const nestedBuf = buf.subarray(f.offset, f.offset + f.size);
|
|
683
|
+
return f.type.get(nestedBuf, fieldName);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
throw new Error(`Unknown field: ${firstPart}`);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Nested struct con dot notation
|
|
691
|
+
if (field.isNested && parts.length > 1) {
|
|
692
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
693
|
+
return field.type.get(nestedBuf, parts.slice(1).join("."));
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
throw new Error(`Unknown field: ${fieldName}`);
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Scrive un campo nella struttura
|
|
701
|
+
* @param {Buffer|Object} bufOrObj - Buffer della struttura O object wrapper
|
|
702
|
+
* @param {string} fieldName - Nome del campo (supporta 'outer.inner' per nested)
|
|
703
|
+
* @param {*} value - Valore da scrivere
|
|
704
|
+
*/
|
|
705
|
+
set(bufOrObj, fieldName, value) {
|
|
706
|
+
// supporta sia buffer che object wrapper o plain object
|
|
707
|
+
let buf;
|
|
708
|
+
if (Buffer.isBuffer(bufOrObj)) {
|
|
709
|
+
buf = bufOrObj;
|
|
710
|
+
} else if (bufOrObj && bufOrObj._buffer) {
|
|
711
|
+
// Object wrapper - accesso diretto tramite property
|
|
712
|
+
bufOrObj[fieldName] = value;
|
|
713
|
+
return;
|
|
714
|
+
} else if (typeof bufOrObj === "object" && bufOrObj !== null) {
|
|
715
|
+
// Plain object (da toObject/create) - accesso diretto
|
|
716
|
+
bufOrObj[fieldName] = value;
|
|
717
|
+
return;
|
|
718
|
+
} else {
|
|
719
|
+
throw new TypeError("Expected Buffer or struct instance");
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Fast path: nome semplice senza dot notation
|
|
723
|
+
// Lookup with Map
|
|
724
|
+
let field = fieldMap.get(fieldName);
|
|
725
|
+
|
|
726
|
+
if (field) {
|
|
727
|
+
// Bit field
|
|
728
|
+
if (field.isBitField) {
|
|
729
|
+
_writeBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, value, sizeof);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Nested struct (senza dot = imposta da oggetto)
|
|
734
|
+
if (field.isNested) {
|
|
735
|
+
if (Buffer.isBuffer(value)) {
|
|
736
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
737
|
+
} else if (typeof value === "object") {
|
|
738
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
739
|
+
for (const [k, v] of Object.entries(value)) {
|
|
740
|
+
field.type.set(nestedBuf, k, v);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Array field
|
|
747
|
+
if (field.isArray) {
|
|
748
|
+
if (Buffer.isBuffer(value)) {
|
|
749
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
750
|
+
} else if (Array.isArray(value)) {
|
|
751
|
+
const arrayBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
752
|
+
const wrapped = field.type.wrap(arrayBuf);
|
|
753
|
+
for (let i = 0; i < Math.min(value.length, field.type.length); i++) {
|
|
754
|
+
wrapped[i] = value[i];
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Tipo base - fast path!
|
|
761
|
+
writeValue(buf, field.type, value, field.offset);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Slow path: supporto per accesso nested 'outer.inner' o anonymous fields
|
|
766
|
+
const parts = fieldName.split(".");
|
|
767
|
+
const firstPart = parts[0];
|
|
768
|
+
|
|
769
|
+
field = fieldMap.get(firstPart);
|
|
770
|
+
|
|
771
|
+
// Se non trovato, cerca negli anonymous fields
|
|
772
|
+
if (!field) {
|
|
773
|
+
for (const f of fieldDefs) {
|
|
774
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
775
|
+
// Verifica se il campo esiste nel tipo anonimo
|
|
776
|
+
const hasField = f.type.fields.some((subField) => subField.name === firstPart);
|
|
777
|
+
if (hasField) {
|
|
778
|
+
// Scrive nel campo dell'anonymous field
|
|
779
|
+
const nestedBuf = buf.subarray(f.offset, f.offset + f.size);
|
|
780
|
+
f.type.set(nestedBuf, fieldName, value);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
throw new Error(`Unknown field: ${firstPart}`);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Bit field
|
|
789
|
+
if (field.isBitField) {
|
|
790
|
+
_writeBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, value, sizeof);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Nested struct
|
|
795
|
+
if (field.isNested) {
|
|
796
|
+
if (parts.length > 1) {
|
|
797
|
+
// Accesso a campo annidato singolo
|
|
798
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
799
|
+
field.type.set(nestedBuf, parts.slice(1).join("."), value);
|
|
800
|
+
} else if (Buffer.isBuffer(value)) {
|
|
801
|
+
// Copia buffer
|
|
802
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
803
|
+
} else if (typeof value === "object") {
|
|
804
|
+
// Imposta campi da oggetto
|
|
805
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
806
|
+
for (const [k, v] of Object.entries(value)) {
|
|
807
|
+
field.type.set(nestedBuf, k, v);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Array field
|
|
814
|
+
if (field.isArray) {
|
|
815
|
+
if (Buffer.isBuffer(value)) {
|
|
816
|
+
// Copia buffer array
|
|
817
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
818
|
+
} else if (Array.isArray(value)) {
|
|
819
|
+
// Inizializza da array JavaScript
|
|
820
|
+
const arrayBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
821
|
+
const wrapped = field.type.wrap(arrayBuf);
|
|
822
|
+
for (let i = 0; i < Math.min(value.length, field.type.length); i++) {
|
|
823
|
+
wrapped[i] = value[i];
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Tipo base
|
|
830
|
+
writeValue(buf, field.type, value, field.offset);
|
|
831
|
+
},
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Legge tutti i campi come oggetto (ricorsivo per nested)
|
|
835
|
+
* @param {Buffer} buf - Buffer della struttura
|
|
836
|
+
* @returns {Object} Oggetto con tutti i campi
|
|
837
|
+
*/
|
|
838
|
+
/**
|
|
839
|
+
* Converte buffer in plain object (KOFFI EAGER APPROACH)
|
|
840
|
+
* Legge TUTTI i valori UNA VOLTA e crea PLAIN properties (no getters)
|
|
841
|
+
* Molto più veloce! Sincronizzazione lazy quando serve _buffer
|
|
842
|
+
*/
|
|
843
|
+
toObject(bufOrObj) {
|
|
844
|
+
// Se è già un object (non buffer), controlla se ha _buffer
|
|
845
|
+
if (!Buffer.isBuffer(bufOrObj)) {
|
|
846
|
+
// Se ha _buffer, estrailo e ricarica i valori (utile dopo modifiche FFI)
|
|
847
|
+
if (bufOrObj && bufOrObj._buffer && Buffer.isBuffer(bufOrObj._buffer)) {
|
|
848
|
+
bufOrObj = bufOrObj._buffer; // Estrai buffer e procedi con il reload
|
|
849
|
+
} else if (typeof bufOrObj === "object" && bufOrObj !== null) {
|
|
850
|
+
// È già un plain object, restituiscilo così com'è
|
|
851
|
+
return bufOrObj;
|
|
852
|
+
} else {
|
|
853
|
+
throw new TypeError("Expected Buffer or struct instance");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const buf = bufOrObj;
|
|
858
|
+
const result = {};
|
|
859
|
+
|
|
860
|
+
// Leggi tutti i campi in una volta (eager approach)
|
|
861
|
+
for (const field of fieldDefs) {
|
|
862
|
+
if (field.isBitField) {
|
|
863
|
+
result[field.name] = _readBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, sizeof);
|
|
864
|
+
} else if (field.isNested) {
|
|
865
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
866
|
+
const nestedObj = field.type.toObject(nestedBuf);
|
|
867
|
+
if (field.isAnonymous) {
|
|
868
|
+
// Anonymous fields: promote their fields to parent level
|
|
869
|
+
Object.assign(result, nestedObj);
|
|
870
|
+
} else {
|
|
871
|
+
result[field.name] = nestedObj;
|
|
872
|
+
}
|
|
873
|
+
} else if (field.isArray) {
|
|
874
|
+
const arrayBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
875
|
+
result[field.name] = field.type.wrap(arrayBuf);
|
|
876
|
+
} else {
|
|
877
|
+
// Tipo base - lettura diretta
|
|
878
|
+
result[field.name] = readValue(buf, field.type, field.offset);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return result;
|
|
883
|
+
},
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Ottiene il buffer di una nested struct
|
|
887
|
+
* @param {Buffer} buf - Buffer della struttura parent
|
|
888
|
+
* @param {string} fieldName - Nome del campo nested
|
|
889
|
+
* @returns {Buffer} Slice del buffer per la nested struct
|
|
890
|
+
*/
|
|
891
|
+
getNestedBuffer(buf, fieldName) {
|
|
892
|
+
// O(1) lookup con Map
|
|
893
|
+
const field = fieldMap.get(fieldName);
|
|
894
|
+
if (!field) throw new Error(`Unknown field: ${fieldName}`);
|
|
895
|
+
if (!field.isNested) throw new Error(`Field ${fieldName} is not a nested struct`);
|
|
896
|
+
return buf.subarray(field.offset, field.offset + field.size);
|
|
897
|
+
},
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
// Crea un'istanza C++ StructType per le operazioni native
|
|
901
|
+
try {
|
|
902
|
+
const cppStructType = new StructType();
|
|
903
|
+
// Per ora non aggiungiamo campi - testiamo se ToObject funziona senza
|
|
904
|
+
structDef._cppStructType = cppStructType;
|
|
905
|
+
} catch (e) {
|
|
906
|
+
// Se fallisce, continua senza C++ (fallback a JS)
|
|
907
|
+
console.warn("Failed to create C++ StructType, using JavaScript fallback:", e.message);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// fromObject: write plain object values into buffer
|
|
911
|
+
structDef.fromObject = function (buf, obj) {
|
|
912
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
913
|
+
if (this.fields.some((f) => f.name === key)) {
|
|
914
|
+
this.set(buf, key, value);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// createRaw: return plain object without synchronization
|
|
920
|
+
structDef.createRaw = function (values = {}) {
|
|
921
|
+
return this.toObject(this.create(values)._buffer);
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
return structDef;
|
|
925
|
+
}
|