expo-vector-search 0.5.0 → 0.5.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/android/build.gradle +3 -3
- package/cpp/ExpoVectorSearch.h +73 -39
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'expo.modules.vectorsearch'
|
|
4
|
-
version = '0.
|
|
4
|
+
version = '0.5.1'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -35,8 +35,8 @@ android {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
defaultConfig {
|
|
38
|
-
versionCode
|
|
39
|
-
versionName "0.
|
|
38
|
+
versionCode 3
|
|
39
|
+
versionName "0.5.1"
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
externalNativeBuild {
|
package/cpp/ExpoVectorSearch.h
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#include <fstream>
|
|
9
9
|
#include <jsi/jsi.h>
|
|
10
10
|
#include <memory>
|
|
11
|
+
#include <mutex>
|
|
11
12
|
#include <string>
|
|
12
13
|
#include <thread>
|
|
13
14
|
#include <unordered_set>
|
|
@@ -192,11 +193,17 @@ public:
|
|
|
192
193
|
jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override {
|
|
193
194
|
std::string methodName = name.utf8(runtime);
|
|
194
195
|
|
|
195
|
-
if (methodName == "dimensions")
|
|
196
|
+
if (methodName == "dimensions") {
|
|
197
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
196
198
|
return jsi::Value((double)_index->dimensions());
|
|
199
|
+
}
|
|
197
200
|
if (methodName == "count")
|
|
201
|
+
{
|
|
202
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
198
203
|
return jsi::Value(_index ? (double)_index->size() : 0);
|
|
204
|
+
}
|
|
199
205
|
if (methodName == "memoryUsage") {
|
|
206
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
200
207
|
if (!_index)
|
|
201
208
|
return jsi::Value(0);
|
|
202
209
|
// We calculate memory usage manually to avoid a race condition in
|
|
@@ -216,6 +223,7 @@ public:
|
|
|
216
223
|
return jsi::Value((double)(vectorBytes + graphOverhead + baseMemory));
|
|
217
224
|
}
|
|
218
225
|
if (methodName == "isa") {
|
|
226
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
219
227
|
const char *isa = _index ? _index->metric().isa_name() : "unknown";
|
|
220
228
|
return jsi::String::createFromUtf8(runtime, isa);
|
|
221
229
|
}
|
|
@@ -237,6 +245,7 @@ public:
|
|
|
237
245
|
runtime, name, 0,
|
|
238
246
|
[this](jsi::Runtime &runtime, const jsi::Value &thisValue,
|
|
239
247
|
const jsi::Value *arguments, size_t count) -> jsi::Value {
|
|
248
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
240
249
|
if (!_lastResult.error.empty()) {
|
|
241
250
|
std::string err = _lastResult.error;
|
|
242
251
|
_lastResult.error = ""; // Clear after reporting
|
|
@@ -254,6 +263,7 @@ public:
|
|
|
254
263
|
runtime, name, 0,
|
|
255
264
|
[this](jsi::Runtime &runtime, const jsi::Value &thisValue,
|
|
256
265
|
const jsi::Value *arguments, size_t count) -> jsi::Value {
|
|
266
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
257
267
|
_index.reset();
|
|
258
268
|
return jsi::Value::undefined();
|
|
259
269
|
});
|
|
@@ -267,15 +277,14 @@ public:
|
|
|
267
277
|
if (count < 2)
|
|
268
278
|
throw jsi::JSError(runtime,
|
|
269
279
|
"add expects 2 arguments: key, vector");
|
|
270
|
-
if (!_index)
|
|
271
|
-
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
272
280
|
|
|
273
281
|
default_key_t key =
|
|
274
282
|
static_cast<default_key_t>(arguments[0].asNumber());
|
|
275
283
|
auto [vecData, vecSize] = getRawVector(runtime, arguments[1]);
|
|
276
284
|
|
|
277
|
-
|
|
278
|
-
|
|
285
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
286
|
+
if (!_index)
|
|
287
|
+
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
279
288
|
|
|
280
289
|
if (vecSize != _index->dimensions()) {
|
|
281
290
|
LOGE("Dimension mismatch: expected %zu, got %zu",
|
|
@@ -349,9 +358,12 @@ public:
|
|
|
349
358
|
throw jsi::JSError(runtime, "Batch mismatch: keys and vectors "
|
|
350
359
|
"must have compatible sizes.");
|
|
351
360
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
_index->
|
|
361
|
+
{
|
|
362
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
363
|
+
if (_index->size() + batchCount > _index->capacity()) {
|
|
364
|
+
size_t newCapacity = _index->size() + batchCount + 100;
|
|
365
|
+
_index->reserve(index_limits_t(newCapacity, _threads));
|
|
366
|
+
}
|
|
355
367
|
}
|
|
356
368
|
|
|
357
369
|
// Copy data safely for background thread
|
|
@@ -369,6 +381,7 @@ public:
|
|
|
369
381
|
auto start = std::chrono::high_resolution_clock::now();
|
|
370
382
|
try {
|
|
371
383
|
for (size_t i = 0; i < batchCount; ++i) {
|
|
384
|
+
std::lock_guard<std::mutex> lock(self->_mutex);
|
|
372
385
|
if (!self->_index)
|
|
373
386
|
break; // Safety check
|
|
374
387
|
auto result = self->_index->add((default_key_t)keys[i],
|
|
@@ -382,12 +395,16 @@ public:
|
|
|
382
395
|
self->_currentIndexingCount++;
|
|
383
396
|
}
|
|
384
397
|
auto end = std::chrono::high_resolution_clock::now();
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
398
|
+
{
|
|
399
|
+
std::lock_guard<std::mutex> lock(self->_mutex);
|
|
400
|
+
self->_lastResult.duration =
|
|
401
|
+
std::chrono::duration<double, std::milli>(end - start)
|
|
402
|
+
.count();
|
|
403
|
+
self->_lastResult.count = batchCount;
|
|
404
|
+
self->_lastResult.error = "";
|
|
405
|
+
}
|
|
390
406
|
} catch (const std::exception &e) {
|
|
407
|
+
std::lock_guard<std::mutex> lock(self->_mutex);
|
|
391
408
|
self->_lastResult.error = e.what();
|
|
392
409
|
}
|
|
393
410
|
self->_isIndexing = false;
|
|
@@ -404,12 +421,14 @@ public:
|
|
|
404
421
|
const jsi::Value *arguments, size_t count) -> jsi::Value {
|
|
405
422
|
if (count < 1)
|
|
406
423
|
throw jsi::JSError(runtime, "remove expects 1 argument: key");
|
|
407
|
-
if (!_index)
|
|
408
|
-
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
409
424
|
|
|
410
425
|
default_key_t key =
|
|
411
426
|
static_cast<default_key_t>(arguments[0].asNumber());
|
|
412
427
|
|
|
428
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
429
|
+
if (!_index)
|
|
430
|
+
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
431
|
+
|
|
413
432
|
auto result = _index->remove(key);
|
|
414
433
|
if (!result) {
|
|
415
434
|
LOGE("Failed to remove vector: %s", result.error.what());
|
|
@@ -429,13 +448,15 @@ public:
|
|
|
429
448
|
if (count < 2)
|
|
430
449
|
throw jsi::JSError(runtime,
|
|
431
450
|
"update expects 2 arguments: key, vector");
|
|
432
|
-
if (!_index)
|
|
433
|
-
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
434
451
|
|
|
435
452
|
default_key_t key =
|
|
436
453
|
static_cast<default_key_t>(arguments[0].asNumber());
|
|
437
454
|
auto [vecData, vecSize] = getRawVector(runtime, arguments[1]);
|
|
438
455
|
|
|
456
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
457
|
+
if (!_index)
|
|
458
|
+
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
459
|
+
|
|
439
460
|
if (vecSize != _index->dimensions()) {
|
|
440
461
|
throw jsi::JSError(runtime, "Incorrect dimension for update.");
|
|
441
462
|
}
|
|
@@ -464,8 +485,6 @@ public:
|
|
|
464
485
|
if (count < 2)
|
|
465
486
|
throw jsi::JSError(runtime,
|
|
466
487
|
"search expects 2 arguments: vector, count");
|
|
467
|
-
if (!_index)
|
|
468
|
-
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
469
488
|
|
|
470
489
|
LOGD("search: starting...");
|
|
471
490
|
auto [queryData, querySize] = getRawVector(runtime, arguments[0]);
|
|
@@ -495,6 +514,10 @@ public:
|
|
|
495
514
|
}
|
|
496
515
|
}
|
|
497
516
|
|
|
517
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
518
|
+
if (!_index)
|
|
519
|
+
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
520
|
+
|
|
498
521
|
if (querySize != _index->dimensions()) {
|
|
499
522
|
LOGE("Search dimension mismatch: expected %zu, got %zu",
|
|
500
523
|
_index->dimensions(), querySize);
|
|
@@ -536,6 +559,11 @@ public:
|
|
|
536
559
|
|
|
537
560
|
default_key_t key =
|
|
538
561
|
static_cast<default_key_t>(arguments[0].asNumber());
|
|
562
|
+
|
|
563
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
564
|
+
if (!_index)
|
|
565
|
+
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
566
|
+
|
|
539
567
|
size_t dims = _index->dimensions();
|
|
540
568
|
|
|
541
569
|
jsi::ArrayBuffer buffer =
|
|
@@ -545,24 +573,15 @@ public:
|
|
|
545
573
|
.getObject(runtime)
|
|
546
574
|
.getArrayBuffer(runtime);
|
|
547
575
|
|
|
548
|
-
// We need raw access to write to it
|
|
549
|
-
// JSI ArrayBuffer doesn't give direct mutable pointer easily
|
|
550
|
-
// without a TypedArray view? Actually getArrayBuffer ->
|
|
551
|
-
// data(runtime) gives pointer.
|
|
552
|
-
|
|
553
576
|
uint8_t *data = buffer.data(runtime);
|
|
554
577
|
float *vecData = reinterpret_cast<float *>(data);
|
|
555
578
|
|
|
556
|
-
// USearch get() signature: bool get(key_t key, scalar_t* vector)
|
|
557
|
-
// const
|
|
558
579
|
bool found = _index->get(key, vecData);
|
|
559
580
|
|
|
560
581
|
if (!found) {
|
|
561
582
|
return jsi::Value::undefined();
|
|
562
583
|
}
|
|
563
584
|
|
|
564
|
-
// Return Float32Array view
|
|
565
|
-
// Float32Array constructor: new Float32Array(buffer)
|
|
566
585
|
jsi::Object float32ArrayCtor =
|
|
567
586
|
runtime.global().getPropertyAsObject(runtime, "Float32Array");
|
|
568
587
|
jsi::Object float32Array = float32ArrayCtor.asFunction(runtime)
|
|
@@ -582,6 +601,9 @@ public:
|
|
|
582
601
|
throw jsi::JSError(runtime, "save expects path");
|
|
583
602
|
std::string path = normalizePath(
|
|
584
603
|
runtime, arguments[0].asString(runtime).utf8(runtime));
|
|
604
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
605
|
+
if (!_index)
|
|
606
|
+
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
585
607
|
if (!_index->save(path.c_str()))
|
|
586
608
|
throw jsi::JSError(
|
|
587
609
|
runtime, "Critical error saving index to disk: " + path);
|
|
@@ -626,27 +648,35 @@ public:
|
|
|
626
648
|
file.read(reinterpret_cast<char *>(vectorData.data()),
|
|
627
649
|
numVectors * dims * sizeof(float));
|
|
628
650
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
651
|
+
{
|
|
652
|
+
std::lock_guard<std::mutex> lock(self->_mutex);
|
|
653
|
+
if (!self->_index)
|
|
654
|
+
return;
|
|
655
|
+
if (self->_index->size() + numVectors >
|
|
656
|
+
self->_index->capacity()) {
|
|
657
|
+
self->_index->reserve(self->_index->size() + numVectors +
|
|
658
|
+
100);
|
|
659
|
+
}
|
|
635
660
|
}
|
|
636
661
|
|
|
637
662
|
for (size_t i = 0; i < numVectors; ++i) {
|
|
663
|
+
std::lock_guard<std::mutex> lock(self->_mutex);
|
|
638
664
|
self->_index->add((default_key_t)i,
|
|
639
665
|
vectorData.data() + (i * dims));
|
|
640
666
|
self->_currentIndexingCount++;
|
|
641
667
|
}
|
|
642
668
|
|
|
643
669
|
auto end = std::chrono::high_resolution_clock::now();
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
670
|
+
{
|
|
671
|
+
std::lock_guard<std::mutex> lock(self->_mutex);
|
|
672
|
+
self->_lastResult.duration =
|
|
673
|
+
std::chrono::duration<double, std::milli>(end - start)
|
|
674
|
+
.count();
|
|
675
|
+
self->_lastResult.count = numVectors;
|
|
676
|
+
self->_lastResult.error = "";
|
|
677
|
+
}
|
|
649
678
|
} catch (const std::exception &e) {
|
|
679
|
+
std::lock_guard<std::mutex> lock(self->_mutex);
|
|
650
680
|
self->_lastResult.error = e.what();
|
|
651
681
|
}
|
|
652
682
|
self->_isIndexing = false;
|
|
@@ -665,6 +695,9 @@ public:
|
|
|
665
695
|
throw jsi::JSError(runtime, "load expects path");
|
|
666
696
|
std::string path = normalizePath(
|
|
667
697
|
runtime, arguments[0].asString(runtime).utf8(runtime));
|
|
698
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
699
|
+
if (!_index)
|
|
700
|
+
throw jsi::JSError(runtime, "VectorIndex has been deleted.");
|
|
668
701
|
if (!_index->load(path.c_str()))
|
|
669
702
|
throw jsi::JSError(
|
|
670
703
|
runtime, "Critical error loading index from disk: " + path);
|
|
@@ -677,6 +710,7 @@ public:
|
|
|
677
710
|
|
|
678
711
|
private:
|
|
679
712
|
std::shared_ptr<Index> _index;
|
|
713
|
+
mutable std::mutex _mutex;
|
|
680
714
|
std::atomic<bool> _isIndexing{false};
|
|
681
715
|
std::atomic<size_t> _currentIndexingCount{0};
|
|
682
716
|
std::atomic<size_t> _totalIndexingCount{0};
|