-
Notifications
You must be signed in to change notification settings - Fork 77
Expand file tree
/
Copy pathJSTypedArray.cs
More file actions
372 lines (324 loc) · 13.1 KB
/
JSTypedArray.cs
File metadata and controls
372 lines (324 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.JavaScript.NodeApi;
//TODO: Add support for Uint8ClampedArray
public readonly struct JSTypedArray<T> : IJSValue<JSTypedArray<T>>
where T : struct
{
private readonly JSValue _value;
/// <summary>
/// Implicitly converts a <see cref="JSTypedArray<T>" /> to a <see cref="JSValue" />.
/// </summary>
/// <param name="value">The <see cref="JSTypedArray<T>" /> to convert.</param>
public static implicit operator JSValue(JSTypedArray<T> array) => array._value;
/// <summary>
/// Explicitly converts a <see cref="JSValue" /> to a
/// nullable <see cref="JSTypedArray<T>" />.
/// </summary>
/// <param name="value">The <see cref="JSValue" /> to convert.</param>
/// <returns>
/// The <see cref="JSTypedArray<T>" /> if it was successfully created or
/// `null` if it was failed.
/// </returns>
public static explicit operator JSTypedArray<T>?(JSValue value) => value.As<JSTypedArray<T>>();
/// <summary>
/// Explicitly converts a <see cref="JSValue" /> to a <see cref="JSTypedArray<T>" />.
/// </summary>
/// <param name="value">The <see cref="JSValue" /> to convert.</param>
/// <returns><see cref="JSTypedArray<T>" /> struct created based on this `JSValue`.</returns>
/// <exception cref="InvalidCastException">
/// Thrown when the T struct cannot be created based on this `JSValue`.
/// </exception>
public static explicit operator JSTypedArray<T>(JSValue value)
=> value.CastTo<JSTypedArray<T>>();
private static uint ElementSize { get; } = default(T) switch
{
sbyte => sizeof(sbyte),
byte => sizeof(byte),
short => sizeof(short),
ushort => sizeof(ushort),
int => sizeof(int),
uint => sizeof(uint),
long => sizeof(long),
ulong => sizeof(ulong),
float => sizeof(float),
double => sizeof(double),
_ => throw new InvalidCastException("Invalid typed-array type: " + typeof(T)),
};
private static JSTypedArrayType ArrayType { get; } = default(T) switch
{
sbyte => JSTypedArrayType.Int8,
byte => JSTypedArrayType.UInt8,
short => JSTypedArrayType.Int16,
ushort => JSTypedArrayType.UInt16,
int => JSTypedArrayType.Int32,
uint => JSTypedArrayType.UInt32,
long => JSTypedArrayType.BigInt64,
ulong => JSTypedArrayType.BigUInt64,
float => JSTypedArrayType.Float32,
double => JSTypedArrayType.Float64,
_ => throw new InvalidCastException("Invalid typed-array type: " + typeof(T)),
};
private static string JSTypeName { get; } = default(T) switch
{
sbyte => "Int8Array",
byte => "Uint8Array",
short => "Int16Array",
ushort => "Uint16Array",
int => "Int32Array",
uint => "Uint32Array",
long => "BigInt64Array",
ulong => "BigUint64Array",
float => "Float32Array",
double => "Float64Array",
_ => throw new InvalidCastException("Invalid typed-array type: " + typeof(T)),
};
private JSTypedArray(JSValue value)
{
_value = value;
}
/// <summary>
/// Creates a new typed array of specified length, with newly allocated memory.
/// </summary>
public JSTypedArray(int length)
{
JSValue arrayBuffer = JSValue.CreateArrayBuffer((uint)length * ElementSize);
_value = JSValue.CreateTypedArray(ArrayType, (uint)length, arrayBuffer, 0);
}
/// <summary>
/// Creates a typed-array over memory, without copying.
/// </summary>
public unsafe JSTypedArray(Memory<T> data)
{
JSValue? value = GetJSValueForMemory(data);
if (value is not null)
{
_value = value.Value;
}
else
{
// The Memory was NOT created from a JS TypedArray. Most likely it was allocated
// directly or via a .NET array or string.
JSValue arrayBuffer = data.Length > 0 ?
JSValue.CreateExternalArrayBuffer(data) : JSValue.CreateArrayBuffer(0);
_value = JSValue.CreateTypedArray(ArrayType, (nuint)data.Length, arrayBuffer, 0);
}
}
/// <summary>
/// Creates a typed-array over read-memory, without copying. Only valid for memory
/// which was previously marshalled from a JS typed-array to .NET.
/// </summary>
/// <exception cref="NotSupportedException">The memory is external to JS.</exception>
public unsafe JSTypedArray(ReadOnlyMemory<T> data)
{
JSValue? value = GetJSValueForMemory(data);
if (value is not null)
{
_value = value.Value;
}
else
{
// Consider copying the memory?
throw new NotSupportedException(
"Read-only memory cannot be transferred from .NET to JS.");
}
}
#region IJSValue<JSTypedArray<T>> implementation
/// <summary>
/// Checks if the T struct can be created from this instance`.
/// </summary>
/// <typeparam name="TOther">A struct that implements IJSValue interface.</typeparam>
/// <returns>
/// `true` if the T struct can be created from this instance. Otherwise it returns `false`.
/// </returns>
public bool Is<TOther>() where TOther : struct, IJSValue<TOther>
=> _value.Is<TOther>();
/// <summary>
/// Tries to create a T struct from this instance.
/// It returns `null` if the T struct cannot be created.
/// </summary>
/// <typeparam name="TOther">A struct that implements IJSValue interface.</typeparam>
/// <returns>
/// Nullable value that contains T struct if it was successfully created
/// or `null` if it was failed.
/// </returns>
public TOther? As<TOther>() where TOther : struct, IJSValue<TOther>
=> _value.As<TOther>();
/// <summary>
/// Creates a T struct from this instance without checking the enclosed handle type.
/// It must be used only when the handle type is known to be correct.
/// </summary>
/// <typeparam name="TOther">A struct that implements IJSValue interface.</typeparam>
/// <returns>T struct created based on this instance.</returns>
public TOther AsUnchecked<TOther>() where TOther : struct, IJSValue<TOther>
=> _value.AsUnchecked<TOther>();
/// <summary>
/// Creates a T struct from this instance.
/// It throws `InvalidCastException` in case of failure.
/// </summary>
/// <typeparam name="TOther">A struct that implements IJSValue interface.</typeparam>
/// <returns>T struct created based on this instance.</returns>
/// <exception cref="InvalidCastException">
/// Thrown when the T struct cannot be crated based on this instance.
/// </exception>
public TOther CastTo<TOther>() where TOther : struct, IJSValue<TOther>
=> _value.CastTo<TOther>();
/// <summary>
/// Determines whether a <see cref="JSTypedArray<T>" /> can be created from
/// the specified <see cref="JSValue" />.
/// </summary>
/// <param name="value">The <see cref="JSValue" /> to check.</param>
/// <returns>
/// <c>true</c> if a <see cref="JSTypedArray<T>" /> can be created from
/// the specified <see cref="JSValue" />; otherwise, <c>false</c>.
/// </returns>
#if NET7_0_OR_GREATER
static bool IJSValue<JSTypedArray<T>>.CanCreateFrom(JSValue value)
#else
#pragma warning disable IDE0051 // It is used by the IJSValueShim<T> class through reflection.
private static bool CanCreateFrom(JSValue value)
#pragma warning restore IDE0051
#endif
=> value.IsObject() && value.InstanceOf(JSValue.Global[JSTypeName]);
/// <summary>
/// Creates a new instance of <see cref="JSTypedArray<T>" /> from
/// the specified <see cref="JSValue" />.
/// </summary>
/// <param name="value">
/// The <see cref="JSValue" /> to create a <see cref="JSTypedArray<T>" /> from.
/// </param>
/// <returns>
/// A new instance of <see cref="JSTypedArray<T>" /> created from
/// the specified <see cref="JSValue" />.
/// </returns>
#if NET7_0_OR_GREATER
static JSTypedArray<T> IJSValue<JSTypedArray<T>>.CreateUnchecked(JSValue value) => new(value);
#else
#pragma warning disable IDE0051 // It is used by the IJSValueShim<T> class through reflection.
private static JSTypedArray<T> CreateUnchecked(JSValue value) => new(value);
#pragma warning restore IDE0051
#endif
#endregion
/// <summary>
/// Checks if this Memory is already owned by a JS TypedArray value, and if so
/// returns that JS value.
/// </summary>
/// <returns>The JS value, or null if the memory is external to JS.</returns>
private static JSValue? GetJSValueForMemory(ReadOnlyMemory<T> data)
{
if (MemoryMarshal.TryGetMemoryManager(data, out MemoryManager? manager, out int index, out int length))
{
// The Memory was created from a JS TypedArray.
JSValue value = manager!.JSValue;
int valueLength = value.GetTypedArrayLength(out _);
if (index != 0 || length != valueLength)
{
// The Memory was sliced, so get an equivalent slice of the JS TypedArray.
value = value.CallMethod("slice", index, index + length);
}
return value;
}
return null;
}
/// <summary>
/// Creates a typed-array over an array, without copying.
/// </summary>
public JSTypedArray(T[] data) : this(data.AsMemory())
{
}
/// <summary>
/// Creates a typed-array over an array, without copying.
/// </summary>
public JSTypedArray(T[] data, int start, int length) : this(data.AsMemory().Slice(start, length))
{
}
public int Length => _value.GetTypedArrayLength(out _);
public T this[int index]
{
get => Span[index];
set => Span[index] = value;
}
/// <summary>
/// Gets the typed-array values as a span, without copying.
/// </summary>
public Span<T> Span => _value.GetTypedArrayData<T>();
/// <summary>
/// Gets the typed-array values as memory, without copying.
/// </summary>
public Memory<T> Memory => new MemoryManager(this).Memory;
/// <summary>
/// Copies the typed-array data into a new array and returns the array.
/// </summary>
public T[] ToArray() => Span.ToArray();
/// <summary>
/// Copies the typed-array data into an array.
/// </summary>
public void CopyTo(T[] array, int arrayIndex)
{
Span.CopyTo(new Span<T>(array, arrayIndex, array.Length - arrayIndex));
}
public Span<T>.Enumerator GetEnumerator() => Span.GetEnumerator();
/// <summary>
/// Compares two JS values using JS "strict" equality.
/// </summary>
public static bool operator ==(JSTypedArray<T> a, JSTypedArray<T> b) => a._value.StrictEquals(b);
/// <summary>
/// Compares two JS values using JS "strict" equality.
/// </summary>
public static bool operator !=(JSTypedArray<T> a, JSTypedArray<T> b) => !a._value.StrictEquals(b);
/// <summary>
/// Compares two JS values using JS "strict" equality.
/// </summary>
public bool Equals(JSValue other) => _value.StrictEquals(other);
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is JSValue other && Equals(other);
}
public override int GetHashCode()
{
throw new NotSupportedException(
"Hashing JS values is not supported. Use JSSet or JSMap instead.");
}
/// <summary>
/// Holds a reference to a typed-array value until the memory is disposed.
/// </summary>
private unsafe class MemoryManager : MemoryManager<T>
{
private readonly void* _pointer;
private readonly int _length;
private readonly JSReference _typedArrayReference;
public MemoryManager(JSTypedArray<T> typedArray)
{
Span<T> span = typedArray.Span;
_pointer = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
_length = span.Length;
_typedArrayReference = new JSReference(typedArray);
}
public JSValue JSValue => _typedArrayReference.GetValue();
public override Span<T> GetSpan()
{
return new Span<T>(_pointer, _length);
}
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
// Do TypedArray or ArrayBuffer support pinning?
// This code assumes the memory buffer is not moveable.
Span<T> span = GetSpan().Slice(elementIndex);
void* pointer = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
return new MemoryHandle(pointer, handle: default, pinnable: this);
}
public override void Unpin() { }
protected override void Dispose(bool disposing)
{
if (disposing)
{
_typedArrayReference.Dispose();
}
}
}
}