File Coverage

Shared.xs
Criterion Covered Total %
statement 370 381 97.1
branch 463 744 62.2
condition n/a
subroutine n/a
pod n/a
total 833 1125 74.0


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #include "ppport.h"
6             #include "sphash.h"
7              
8             #define EXTRACT(sv) \
9             if (!sv_isobject(sv) || !sv_derived_from(sv, "Data::SpatialHash::Shared")) \
10             croak("Expected a Data::SpatialHash::Shared object"); \
11             SpatialHandle *h = INT2PTR(SpatialHandle*, SvIV(SvRV(sv))); \
12             if (!h) croak("Attempted to use a destroyed Data::SpatialHash::Shared object")
13              
14             #define MAKE_OBJ(class, handle) \
15             SV *obj = newSViv(PTR2IV(handle)); \
16             SV *ref = newRV_noinc(obj); \
17             sv_bless(ref, gv_stashpv(class, GV_ADD)); \
18             RETVAL = ref
19              
20             #define REQUIRE_LIVE(h, idx) do { \
21             if (!sph_is_live((h), (idx))) { sph_rwlock_wrunlock(h); \
22             croak("invalid or freed handle %u", (unsigned)(idx)); } \
23             } while (0)
24              
25             /* read-lock counterpart of REQUIRE_LIVE (releases the read lock before croaking) */
26             #define REQUIRE_LIVE_RD(h, idx) do { \
27             if (!sph_is_live((h), (idx))) { sph_rwlock_rdunlock(h); \
28             croak("invalid or freed handle %u", (unsigned)(idx)); } \
29             } while (0)
30              
31             /* Shared croak message for the SPH_Q_TOOBIG cap (EMIT_QUERY, EMIT_PAIRS, each_in_radius, query_radius_many);
32             takes (unsigned)SPH_MAX_QUERY_CELLS as its %u argument. */
33             #define SPH_TOOBIG_MSG "query region spans more than %u cells; increase cell_size or shrink the query"
34              
35             /* Run a collector-filling query under the read lock, then push its values as
36             mortal IVs. `CALL` must be an expression filling sph_collect_t `col`. */
37             #define EMIT_QUERY(CALL) do { \
38             sph_collect_t col = { NULL, 0, 0 }; \
39             sph_rwlock_rdlock(h); \
40             int rc = (CALL); \
41             sph_rwlock_rdunlock(h); \
42             if (rc == SPH_Q_OOM) { free(col.vals); croak("query: out of memory"); } \
43             if (rc == SPH_Q_TOOBIG) { free(col.vals); croak(SPH_TOOBIG_MSG, (unsigned)SPH_MAX_QUERY_CELLS); } \
44             EXTEND(SP, (SSize_t)col.n); \
45             for (size_t i = 0; i < col.n; i++) PUSHs(sv_2mortal(newSViv((IV)col.vals[i]))); \
46             free(col.vals); \
47             } while (0)
48              
49             /* Collect pairs (flattened va,vb,...) under the read lock, then -- after the
50             lock is released -- invoke `cb` once per pair. G_EVAL so a die in the callback
51             still frees the buffer; re-throw after cleanup (matches each_in_radius). */
52             #define EMIT_PAIRS(CALL) do { \
53             sph_collect_t col = { NULL, 0, 0 }; \
54             sph_rwlock_rdlock(h); \
55             int rc = (CALL); \
56             sph_rwlock_rdunlock(h); \
57             if (rc == SPH_Q_OOM) { free(col.vals); croak("each pair: out of memory"); } \
58             if (rc == SPH_Q_TOOBIG) { free(col.vals); croak(SPH_TOOBIG_MSG, (unsigned)SPH_MAX_QUERY_CELLS); } \
59             for (size_t i = 0; i + 1 < col.n; i += 2) { \
60             dSP; ENTER; SAVETMPS; PUSHMARK(SP); \
61             XPUSHs(sv_2mortal(newSViv((IV)col.vals[i]))); \
62             XPUSHs(sv_2mortal(newSViv((IV)col.vals[i+1]))); \
63             PUTBACK; \
64             call_sv(cb, G_VOID|G_DISCARD|G_EVAL); \
65             FREETMPS; LEAVE; \
66             if (SvTRUE(ERRSV)) { free(col.vals); croak_sv(ERRSV); } \
67             } \
68             free(col.vals); \
69             } while (0)
70              
71             /* parse optional trailing "wrap => [Wx, Wy(, Wz)]" and "sphere => R" args;
72             returns world pointer (or NULL) and sets *sphere if a sphere radius is given */
73 95           static const double *sph_parse_opts(pTHX_ SV **sp, int first, int items, double world[3], double *sphere, const char *who) {
74 95           const double *wp = NULL;
75 95 50         if ((items - first) % 2 != 0) croak("%s: odd number of option arguments", who);
76 111 100         for (int ai = first; ai + 1 < items; ai += 2) {
77 22           const char *key = SvPV_nolen(sp[ai]);
78 22 100         if (strcmp(key, "wrap") == 0) {
79 11           SV *wv = sp[ai + 1];
80 11 50         if (!SvROK(wv) || SvTYPE(SvRV(wv)) != SVt_PVAV)
    50          
81 0           croak("%s: wrap must be an arrayref [Wx, Wy] or [Wx, Wy, Wz]", who);
82 11           AV *av = (AV *)SvRV(wv);
83 11           SSize_t n = av_len(av) + 1;
84 11 100         if (n < 2 || n > 3) croak("%s: wrap needs 2 or 3 extents", who);
    50          
85 10           world[0] = world[1] = world[2] = 0.0;
86 32 50         for (SSize_t i = 0; i < n; i++) { SV **e = av_fetch(av, i, 0); world[(int)i] = e ? SvNV(*e) : 0.0; }
    100          
87 10           wp = world;
88 11 100         } else if (strcmp(key, "sphere") == 0) {
89 10           *sphere = (double)SvNV(sp[ai + 1]);
90 10 100         if (!(*sphere > 0.0) || !isfinite(*sphere)) croak("%s: sphere radius must be finite and > 0", who);
    100          
91             } else {
92 1           croak("%s: unknown option '%s'", who, key);
93             }
94             }
95 89           return wp;
96             }
97              
98             MODULE = Data::SpatialHash::Shared PACKAGE = Data::SpatialHash::Shared
99              
100             PROTOTYPES: DISABLE
101              
102             SV *
103             new(class, path, max_entries, num_buckets, cell_size, ...)
104             const char *class
105             SV *path
106             UV max_entries
107             UV num_buckets
108             NV cell_size
109             PREINIT:
110             char errbuf[SPH_ERR_BUFLEN];
111 93           double world[3] = {0,0,0};
112 93           double sphere_radius = 0;
113             CODE:
114 93 100         const char *p = SvOK(path) ? SvPV_nolen(path) : NULL;
115 93 50         if (max_entries > UINT32_MAX || num_buckets > UINT32_MAX)
    50          
116 0           croak("Data::SpatialHash::Shared->new: max_entries/num_buckets exceed 2^32");
117 93           const double *worldp = sph_parse_opts(aTHX_ &ST(0), 5, items, world, &sphere_radius,
118             "Data::SpatialHash::Shared->new");
119 87           SpatialHandle *h = sph_create(p, (uint32_t)max_entries, (uint32_t)num_buckets,
120             (double)cell_size, worldp, sphere_radius, errbuf);
121 87 100         if (!h) croak("Data::SpatialHash::Shared->new: %s", errbuf);
122 75           MAKE_OBJ(class, h);
123             OUTPUT:
124             RETVAL
125              
126             SV *
127             new_memfd(class, name, max_entries, num_buckets, cell_size, ...)
128             const char *class
129             const char *name
130             UV max_entries
131             UV num_buckets
132             NV cell_size
133             PREINIT:
134             char errbuf[SPH_ERR_BUFLEN];
135 2           double world[3] = {0,0,0};
136 2           double sphere_radius = 0;
137             CODE:
138 2 50         if (max_entries > UINT32_MAX || num_buckets > UINT32_MAX)
    50          
139 0           croak("Data::SpatialHash::Shared->new_memfd: max_entries/num_buckets exceed 2^32");
140 2           const double *worldp = sph_parse_opts(aTHX_ &ST(0), 5, items, world, &sphere_radius,
141             "Data::SpatialHash::Shared->new_memfd");
142 2           SpatialHandle *h = sph_create_memfd(name, (uint32_t)max_entries, (uint32_t)num_buckets,
143             (double)cell_size, worldp, sphere_radius, errbuf);
144 2 50         if (!h) croak("Data::SpatialHash::Shared->new_memfd: %s", errbuf);
145 2           MAKE_OBJ(class, h);
146             OUTPUT:
147             RETVAL
148              
149             SV *
150             new_from_fd(class, fd)
151             const char *class
152             int fd
153             PREINIT:
154             char errbuf[SPH_ERR_BUFLEN];
155             CODE:
156 2           SpatialHandle *h = sph_open_fd(fd, errbuf);
157 2 50         if (!h) croak("Data::SpatialHash::Shared->new_from_fd: %s", errbuf);
158 2           MAKE_OBJ(class, h);
159             OUTPUT:
160             RETVAL
161              
162             void
163             DESTROY(self)
164             SV *self
165             CODE:
166 79 50         if (!SvROK(self)) return;
167 79           SpatialHandle *h = INT2PTR(SpatialHandle*, SvIV(SvRV(self)));
168 79 50         if (!h) return;
169 79           sv_setiv(SvRV(self), 0);
170 79           sph_destroy(h);
171              
172             void
173             sync(self)
174             SV *self
175             PREINIT:
176 9 50         EXTRACT(self);
    50          
    50          
177             CODE:
178 9 50         if (sph_msync(h) != 0) croak("msync: %s", strerror(errno));
179              
180             void
181             unlink(self_or_class, ...)
182             SV *self_or_class
183             CODE:
184 3           const char *p = NULL;
185 3 100         if (sv_isobject(self_or_class)) {
186 2           SpatialHandle *h = INT2PTR(SpatialHandle*, SvIV(SvRV(self_or_class)));
187 2 50         if (!h) croak("Attempted to use a destroyed object");
188 2           p = h->path;
189             } else {
190 1 50         if (items < 2) croak("Usage: ...->unlink($path)");
191 1           p = SvPV_nolen(ST(1));
192             }
193 3 50         if (!p) croak("cannot unlink anonymous or memfd object");
194 3 50         if (unlink(p) != 0) croak("unlink(%s): %s", p, strerror(errno));
195              
196             IV
197             memfd(self)
198             SV *self
199             PREINIT:
200 2 50         EXTRACT(self);
    50          
    50          
201             CODE:
202 2 50         RETVAL = h->backing_fd;
203             OUTPUT:
204             RETVAL
205              
206             IV
207             eventfd(self)
208             SV *self
209             PREINIT:
210 2 50         EXTRACT(self);
    50          
    50          
211             CODE:
212 2           RETVAL = sph_create_eventfd(h);
213 2 50         if (RETVAL < 0) croak("eventfd: %s", strerror(errno));
214             OUTPUT:
215             RETVAL
216              
217             void
218             eventfd_set(self, fd)
219             SV *self
220             int fd
221             PREINIT:
222 1 50         EXTRACT(self);
    50          
    50          
223             CODE:
224 1 50         if (h->notify_fd >= 0 && h->notify_fd != fd) close(h->notify_fd);
    50          
225 1           h->notify_fd = fd;
226              
227             IV
228             fileno(self)
229             SV *self
230             PREINIT:
231 3 50         EXTRACT(self);
    50          
    50          
232             CODE:
233 3 50         RETVAL = h->notify_fd;
234             OUTPUT:
235             RETVAL
236              
237             bool
238             notify(self)
239             SV *self
240             PREINIT:
241 2 50         EXTRACT(self);
    50          
    50          
242             CODE:
243 2 50         RETVAL = sph_notify(h);
244             OUTPUT:
245             RETVAL
246              
247             SV *
248             eventfd_consume(self)
249             SV *self
250             PREINIT:
251 3 50         EXTRACT(self);
    50          
    50          
252             CODE:
253 3           int64_t n = sph_eventfd_consume(h);
254 3 100         RETVAL = (n >= 0) ? newSViv((IV)n) : &PL_sv_undef;
255             OUTPUT:
256             RETVAL
257              
258             UV
259             max_entries(self)
260             SV *self
261             PREINIT:
262 3 50         EXTRACT(self);
    50          
    50          
263             CODE:
264 3 50         RETVAL = h->hdr->max_entries;
265             OUTPUT:
266             RETVAL
267              
268             UV
269             num_buckets(self)
270             SV *self
271             PREINIT:
272 7 50         EXTRACT(self);
    50          
    50          
273             CODE:
274 7 100         RETVAL = h->hdr->num_buckets;
275             OUTPUT:
276             RETVAL
277              
278             NV
279             cell_size(self)
280             SV *self
281             PREINIT:
282 2 50         EXTRACT(self);
    50          
    50          
283             CODE:
284 2 50         RETVAL = h->hdr->cell_size;
285             OUTPUT:
286             RETVAL
287              
288             void
289             world(self)
290             SV *self
291             PREINIT:
292 6 50         EXTRACT(self);
    50          
    50          
293             PPCODE:
294 6 100         if (h->wrap) {
295 5 100         int dims = (h->hdr->world[2] > 0.0) ? 3 : 2;
296 5 50         EXTEND(SP, dims);
    50          
297 17 100         for (int i = 0; i < dims; i++) PUSHs(sv_2mortal(newSVnv(h->hdr->world[i])));
298             }
299              
300             NV
301             sphere(self)
302             SV *self
303             PREINIT:
304 3 50         EXTRACT(self);
    50          
    50          
305             CODE:
306 3 50         RETVAL = h->hdr->sphere_radius;
307             OUTPUT:
308             RETVAL
309              
310             UV
311             count(self)
312             SV *self
313             PREINIT:
314 64 50         EXTRACT(self);
    50          
    50          
315             CODE:
316 64 100         RETVAL = __atomic_load_n(&h->hdr->count, __ATOMIC_ACQUIRE);
317             OUTPUT:
318             RETVAL
319              
320             SV *
321             path(self)
322             SV *self
323             PREINIT:
324 2 50         EXTRACT(self);
    50          
    50          
325             CODE:
326 2 100         RETVAL = h->path ? newSVpv(h->path, 0) : &PL_sv_undef;
327             OUTPUT:
328             RETVAL
329              
330             SV *
331             insert(self, x, y, ...)
332             SV *self
333             NV x
334             NV y
335             PREINIT:
336 9094 50         EXTRACT(self);
    50          
    50          
337             double z, radius;
338             int64_t val;
339             uint32_t idx;
340             CODE:
341             /* (x,y,value)=4 2D ; (x,y,z,value)=5 3D ; (x,y,z,value,radius)=6 3D+radius */
342 9094           z = 0; radius = 0;
343 9094 100         if (items == 4) { val = (int64_t)SvIV(ST(3)); }
344 3913 100         else if (items == 5) { z = (double)SvNV(ST(3)); val = (int64_t)SvIV(ST(4)); }
345 1 50         else if (items == 6) { z = (double)SvNV(ST(3)); val = (int64_t)SvIV(ST(4)); radius = (double)SvNV(ST(5)); }
346 0           else croak("insert: expected (x,y,value), (x,y,z,value), or (x,y,z,value,radius)");
347 9094 50         if (radius < 0 || !isfinite(radius)) croak("insert: radius must be a finite number >= 0");
    50          
348 9094           sph_rwlock_wrlock(h);
349 9094           idx = sph_insert_locked(h, x, y, z, val, radius);
350 9094           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
351 9094           sph_rwlock_wrunlock(h);
352 9094 100         RETVAL = (idx == SPH_NONE) ? &PL_sv_undef : newSVuv(idx);
353             OUTPUT:
354             RETVAL
355              
356             bool
357             move(self, handle, x, y, ...)
358             SV *self
359             UV handle
360             NV x
361             NV y
362             PREINIT:
363 657 50         EXTRACT(self);
    50          
    50          
364             double z;
365             CODE:
366 657           z = 0;
367 657 100         if (items == 5) z = (double)SvNV(ST(4));
368 6 50         else if (items != 4) croak("move: expected (handle,x,y) or (handle,x,y,z)");
369 657           sph_rwlock_wrlock(h);
370 657           RETVAL = sph_move_locked(h, (uint32_t)handle, x, y, z);
371 657           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
372 657           sph_rwlock_wrunlock(h);
373             OUTPUT:
374             RETVAL
375              
376             bool
377             remove(self, handle)
378             SV *self
379             UV handle
380             PREINIT:
381 463 50         EXTRACT(self);
    50          
    50          
382             CODE:
383 463           sph_rwlock_wrlock(h);
384 463           RETVAL = sph_remove_locked(h, (uint32_t)handle);
385 463           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
386 463           sph_rwlock_wrunlock(h);
387             OUTPUT:
388             RETVAL
389              
390             bool
391             has(self, handle)
392             SV *self
393             UV handle
394             PREINIT:
395 14 50         EXTRACT(self);
    50          
    50          
396             CODE:
397 14           sph_rwlock_rdlock(h);
398 14           RETVAL = sph_is_live(h, (uint32_t)handle);
399 14           sph_rwlock_rdunlock(h);
400             OUTPUT:
401             RETVAL
402              
403             IV
404             value(self, handle)
405             SV *self
406             UV handle
407             PREINIT:
408 11 50         EXTRACT(self);
    50          
    50          
409             CODE:
410 11           sph_rwlock_rdlock(h);
411 11 100         REQUIRE_LIVE_RD(h, (uint32_t)handle);
412 9           RETVAL = (IV)h->entries[(uint32_t)handle].value;
413 9           sph_rwlock_rdunlock(h);
414             OUTPUT:
415             RETVAL
416              
417             void
418             set_value(self, handle, v)
419             SV *self
420             UV handle
421             IV v
422             PREINIT:
423 3 50         EXTRACT(self);
    50          
    50          
424             CODE:
425 3           sph_rwlock_wrlock(h);
426 3 100         REQUIRE_LIVE(h, (uint32_t)handle);
427 1           h->entries[(uint32_t)handle].value = (int64_t)v;
428 1           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
429 1           sph_rwlock_wrunlock(h);
430              
431             void
432             set_radius(self, handle, radius)
433             SV *self
434             UV handle
435             NV radius
436             PREINIT:
437 454 50         EXTRACT(self);
    50          
    50          
438             CODE:
439 454 50         if (radius < 0 || !isfinite(radius)) croak("set_radius: radius must be a finite number >= 0");
    50          
440 454           sph_rwlock_wrlock(h);
441 454 100         REQUIRE_LIVE(h, (uint32_t)handle);
442 453           h->entries[(uint32_t)handle].radius = (double)radius;
443 453           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
444 453           sph_rwlock_wrunlock(h);
445              
446             NV
447             get_radius(self, handle)
448             SV *self
449             UV handle
450             PREINIT:
451 8 50         EXTRACT(self);
    50          
    50          
452             CODE:
453 8           sph_rwlock_rdlock(h);
454 8 100         REQUIRE_LIVE_RD(h, (uint32_t)handle);
455 6           RETVAL = h->entries[(uint32_t)handle].radius;
456 6           sph_rwlock_rdunlock(h);
457             OUTPUT:
458             RETVAL
459              
460             IV
461             move_many(self, rows)
462             SV *self
463             SV *rows
464             PREINIT:
465 4 50         EXTRACT(self);
    50          
    50          
466             IV moved;
467             CODE:
468 4 50         if (!SvROK(rows) || SvTYPE(SvRV(rows)) != SVt_PVAV)
    50          
469 0           croak("move_many: expected an arrayref of [handle,x,y] or [handle,x,y,z]");
470             {
471 4           AV *av = (AV *)SvRV(rows);
472 4           SSize_t nr = av_len(av) + 1;
473 4           moved = 0;
474 4           sph_rwlock_wrlock(h);
475 12 100         for (SSize_t i = 0; i < nr; i++) {
476 8           SV **rv = av_fetch(av, i, 0);
477 8 50         if (!rv || !SvROK(*rv) || SvTYPE(SvRV(*rv)) != SVt_PVAV) continue;
    50          
    50          
478 8           AV *row = (AV *)SvRV(*rv);
479 8           SSize_t rl = av_len(row) + 1;
480 8 100         if (rl != 3 && rl != 4) continue;
    50          
481 8           SV **hp = av_fetch(row, 0, 0), **xp = av_fetch(row, 1, 0), **yp = av_fetch(row, 2, 0);
482 8 100         SV **zp = (rl == 4) ? av_fetch(row, 3, 0) : NULL;
483 8 50         if (!hp || !xp || !yp) continue;
    50          
    50          
484 8 100         if (sph_move_locked(h, (uint32_t)SvUV(*hp), SvNV(*xp), SvNV(*yp), zp ? SvNV(*zp) : 0.0)) moved++;
    100          
485             }
486 4 100         if (nr > 0) __atomic_fetch_add(&h->hdr->stat_ops, (uint64_t)nr, __ATOMIC_RELAXED);
487 4           sph_rwlock_wrunlock(h);
488             }
489 4 50         RETVAL = moved;
490             OUTPUT:
491             RETVAL
492              
493             void
494             insert_many(self, rows)
495             SV *self
496             SV *rows
497             PREINIT:
498 4 50         EXTRACT(self);
    50          
    50          
499             PPCODE:
500 4 50         if (!SvROK(rows) || SvTYPE(SvRV(rows)) != SVt_PVAV)
    50          
501 0           croak("insert_many: expected an arrayref of [x,y,value] or [x,y,value,radius]");
502             {
503 4           AV *av = (AV *)SvRV(rows);
504 4           SSize_t nr = av_len(av) + 1;
505 4 50         EXTEND(SP, nr);
    50          
506 4           sph_rwlock_wrlock(h);
507 11 100         for (SSize_t i = 0; i < nr; i++) {
508 7           uint32_t idx = SPH_NONE;
509 7           SV **rv = av_fetch(av, i, 0);
510 7 50         if (rv && SvROK(*rv) && SvTYPE(SvRV(*rv)) == SVt_PVAV) {
    50          
    50          
511 7           AV *row = (AV *)SvRV(*rv);
512 7           SSize_t rl = av_len(row) + 1;
513 7 100         if (rl == 3 || rl == 4) {
    100          
514 6           SV **xp = av_fetch(row, 0, 0), **yp = av_fetch(row, 1, 0), **vp = av_fetch(row, 2, 0);
515 6 100         SV **rp = (rl == 4) ? av_fetch(row, 3, 0) : NULL;
516 6 50         if (xp && yp && vp) {
    50          
    50          
517 6 100         double rad = rp ? SvNV(*rp) : 0.0;
518             /* skip a row with a bad radius (-> undef handle), like other
519             malformed rows; can't croak here -- we hold the write lock */
520 6 50         if (rad >= 0 && isfinite(rad))
    50          
521 6           idx = sph_insert_locked(h, SvNV(*xp), SvNV(*yp), 0.0,
522 6           (int64_t)SvIV(*vp), rad);
523             }
524             }
525             }
526 7 100         PUSHs(idx == SPH_NONE ? &PL_sv_undef : sv_2mortal(newSVuv(idx)));
527             }
528 4 100         if (nr > 0) __atomic_fetch_add(&h->hdr->stat_ops, (uint64_t)nr, __ATOMIC_RELAXED);
529 4           sph_rwlock_wrunlock(h);
530             }
531              
532             void
533             position(self, handle)
534             SV *self
535             UV handle
536             PREINIT:
537 13 50         EXTRACT(self);
    50          
    50          
538             double px, py, pz;
539             PPCODE:
540 13           sph_rwlock_rdlock(h);
541 13 100         REQUIRE_LIVE_RD(h, (uint32_t)handle);
542 11           px = h->entries[(uint32_t)handle].pos[0];
543 11           py = h->entries[(uint32_t)handle].pos[1];
544 11           pz = h->entries[(uint32_t)handle].pos[2];
545 11           sph_rwlock_rdunlock(h);
546 11 50         EXTEND(SP, 3);
547 11           PUSHs(sv_2mortal(newSVnv(px)));
548 11           PUSHs(sv_2mortal(newSVnv(py)));
549 11           PUSHs(sv_2mortal(newSVnv(pz)));
550              
551             void
552             query_cell(self, x, y, ...)
553             SV *self
554             NV x
555             NV y
556             PREINIT:
557 17 50         EXTRACT(self);
    50          
    50          
558             PPCODE:
559 17 100         if (items != 3 && items != 4) croak("query_cell: (x,y) or (x,y,z)");
    50          
560 17 100         int dims = (items == 4) ? 3 : 2;
561 17 100         double p[3] = { x, y, dims==3 ? (double)SvNV(ST(3)) : 0 };
562 41 50         EMIT_QUERY(sph_query_cell(h, p, dims, &col));
    50          
    50          
    50          
    100          
563              
564             void
565             query_aabb(self, ...)
566             SV *self
567             PREINIT:
568 12 50         EXTRACT(self);
    50          
    50          
569             PPCODE:
570             double lo[3], hi[3]; int dims;
571 12 100         if (items == 5) { dims = 2;
572 9           lo[0]=SvNV(ST(1)); lo[1]=SvNV(ST(2)); hi[0]=SvNV(ST(3)); hi[1]=SvNV(ST(4)); lo[2]=hi[2]=0;
573 3 50         } else if (items == 7) { dims = 3;
574 3           lo[0]=SvNV(ST(1)); lo[1]=SvNV(ST(2)); lo[2]=SvNV(ST(3));
575 3           hi[0]=SvNV(ST(4)); hi[1]=SvNV(ST(5)); hi[2]=SvNV(ST(6));
576 0           } else croak("query_aabb: (x0,y0,x1,y1) or (x0,y0,z0,x1,y1,z1)");
577 145 50         EMIT_QUERY(sph_query_aabb(h, lo, hi, dims, &col));
    100          
    50          
    50          
    100          
578              
579             void
580             query_radius(self, ...)
581             SV *self
582             PREINIT:
583 192 50         EXTRACT(self);
    50          
    50          
584             PPCODE:
585 192           double c[3] = {0,0,0}, r; int dims;
586 192 100         if (items == 4) { dims = 2; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); r=SvNV(ST(3)); }
587 86 50         else if (items == 5) { dims = 3; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); c[2]=SvNV(ST(3)); r=SvNV(ST(4)); }
588 0           else croak("query_radius: (x,y,r) or (x,y,z,r)");
589 192 50         if (r < 0 || !isfinite(r)) croak("query_radius: r must be a finite number >= 0");
    50          
590 2347 50         EMIT_QUERY(sph_query_radius(h, c, r, dims, &col));
    100          
    50          
    50          
    100          
591              
592             # Batched broad-phase: N radius queries under ONE read lock. Returns an arrayref of
593             # id-list arrayrefs, one per query in input order (each == [query_radius(@$q)]). A
594             # malformed row (not a 3/4-elem arrayref, or a negative/non-finite r) yields an empty
595             # list for that slot -- can't croak under the lock, mirrors insert_many. OOM/TOOBIG
596             # from any query croak after freeing the partial result tree.
597             SV *
598             query_radius_many(self, queries)
599             SV *self
600             SV *queries
601             PREINIT:
602 7 50         EXTRACT(self);
    50          
    50          
603             CODE:
604 7 50         if (!SvROK(queries) || SvTYPE(SvRV(queries)) != SVt_PVAV)
    50          
605 0           croak("query_radius_many: expected an arrayref of [x,y,r] or [x,y,z,r]");
606             {
607 7           AV *qav = (AV *)SvRV(queries);
608 7           SSize_t nq = av_len(qav) + 1;
609 7           AV *out = newAV();
610 7 100         if (nq > 0) av_extend(out, nq - 1);
611 7           int err = 0; /* 0 ok, 1 OOM, 2 TOOBIG */
612 7           sph_rwlock_rdlock(h); /* one lock for the whole batch */
613 110 100         for (SSize_t i = 0; i < nq && !err; i++) {
    50          
614 103           AV *res = newAV();
615 103           SV **qp = av_fetch(qav, i, 0);
616 103 50         if (qp && SvROK(*qp) && SvTYPE(SvRV(*qp)) == SVt_PVAV) {
    100          
    50          
617 102           AV *q = (AV *)SvRV(*qp);
618 102           SSize_t ql = av_len(q) + 1;
619 102           double c[3] = {0,0,0}, r = 0; int dims = 0;
620 102 100         if (ql == 3) {
621 71           SV **x=av_fetch(q,0,0), **y=av_fetch(q,1,0), **rr=av_fetch(q,2,0);
622 71 50         if (x && y && rr) { dims=2; c[0]=SvNV(*x); c[1]=SvNV(*y); r=SvNV(*rr); }
    50          
    50          
623 31 100         } else if (ql == 4) {
624 30           SV **x=av_fetch(q,0,0), **y=av_fetch(q,1,0), **z=av_fetch(q,2,0), **rr=av_fetch(q,3,0);
625 30 50         if (x && y && z && rr) { dims=3; c[0]=SvNV(*x); c[1]=SvNV(*y); c[2]=SvNV(*z); r=SvNV(*rr); }
    50          
    50          
    50          
626             }
627 102 100         if (dims && r >= 0 && isfinite(r)) {
    100          
    100          
628 99           sph_collect_t col = { NULL, 0, 0 };
629 99           int rc = sph_query_radius(h, c, r, dims, &col);
630 99 50         if (rc == SPH_Q_OOM) { free(col.vals); err = 1; }
631 99 100         else if (rc == SPH_Q_TOOBIG) { free(col.vals); err = 2; }
632             else {
633 98 100         if (col.n) av_extend(res, (SSize_t)col.n - 1);
634 1646 100         for (size_t k = 0; k < col.n; k++) av_push(res, newSViv((IV)col.vals[k]));
635 98           free(col.vals);
636             }
637             }
638             /* else: malformed query -> empty res (cannot croak under the lock) */
639             }
640 103           av_push(out, newRV_noinc((SV *)res)); /* out owns res, even on the error path */
641             }
642 7           sph_rwlock_rdunlock(h);
643 7 100         if (err) {
644 1           SvREFCNT_dec((SV *)out); /* frees out + every res pushed so far */
645 1 50         if (err == 1) croak("query_radius_many: out of memory");
646 1           croak(SPH_TOOBIG_MSG, (unsigned)SPH_MAX_QUERY_CELLS);
647             }
648 6           RETVAL = newRV_noinc((SV *)out);
649             }
650             OUTPUT:
651             RETVAL
652              
653             SV *
654             insert_geo(self, lat, lon, alt, value)
655             SV *self
656             NV lat
657             NV lon
658             NV alt
659             IV value
660             PREINIT:
661 807 50         EXTRACT(self);
    50          
    50          
662             double xyz[3];
663             uint32_t idx;
664             CODE:
665 807 100         if (!(h->hdr->sphere_radius > 0.0)) croak("insert_geo: map was not created with sphere => R");
666 806           sph_geo_to_xyz(h->hdr->sphere_radius, lat, lon, alt, xyz);
667 806           sph_rwlock_wrlock(h);
668 806           idx = sph_insert_locked(h, xyz[0], xyz[1], xyz[2], (int64_t)value, 0.0);
669 806           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
670 806           sph_rwlock_wrunlock(h);
671 806 50         RETVAL = (idx == SPH_NONE) ? &PL_sv_undef : newSVuv(idx);
672             OUTPUT:
673             RETVAL
674              
675             bool
676             move_geo(self, handle, lat, lon, alt)
677             SV *self
678             UV handle
679             NV lat
680             NV lon
681             NV alt
682             PREINIT:
683 3 50         EXTRACT(self);
    50          
    50          
684             double xyz[3];
685             CODE:
686 3 100         if (!(h->hdr->sphere_radius > 0.0)) croak("move_geo: map was not created with sphere => R");
687 2           sph_geo_to_xyz(h->hdr->sphere_radius, lat, lon, alt, xyz);
688 2           sph_rwlock_wrlock(h);
689 2           RETVAL = sph_move_locked(h, (uint32_t)handle, xyz[0], xyz[1], xyz[2]);
690 2           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
691 2           sph_rwlock_wrunlock(h);
692             OUTPUT:
693             RETVAL
694              
695             void
696             position_geo(self, handle)
697             SV *self
698             UV handle
699             PREINIT:
700 307 50         EXTRACT(self);
    50          
    50          
701             double p[3], lat, lon, alt;
702             PPCODE:
703 307 100         if (!(h->hdr->sphere_radius > 0.0)) croak("position_geo: map was not created with sphere => R");
704 306           sph_rwlock_rdlock(h);
705 306 100         REQUIRE_LIVE_RD(h, (uint32_t)handle);
706 305           p[0] = h->entries[(uint32_t)handle].pos[0];
707 305           p[1] = h->entries[(uint32_t)handle].pos[1];
708 305           p[2] = h->entries[(uint32_t)handle].pos[2];
709 305           sph_rwlock_rdunlock(h);
710 305           sph_geo_of_xyz(h->hdr->sphere_radius, p, &lat, &lon, &alt);
711 305 50         EXTEND(SP, 3);
712 305           PUSHs(sv_2mortal(newSVnv(lat)));
713 305           PUSHs(sv_2mortal(newSVnv(lon)));
714 305           PUSHs(sv_2mortal(newSVnv(alt)));
715              
716             void
717             query_geo_radius(self, lat, lon, alt, dist)
718             SV *self
719             NV lat
720             NV lon
721             NV alt
722             NV dist
723             PREINIT:
724 27 50         EXTRACT(self);
    50          
    50          
725             double c[3];
726             PPCODE:
727 27 100         if (!(h->hdr->sphere_radius > 0.0)) croak("query_geo_radius: map was not created with sphere => R");
728 26 100         if (dist < 0 || !isfinite(dist)) croak("query_geo_radius: dist must be a finite number >= 0");
    50          
729 25           sph_geo_to_xyz(h->hdr->sphere_radius, lat, lon, alt, c);
730 318 50         EMIT_QUERY(sph_query_radius(h, c, (double)dist, 3, &col));
    50          
    50          
    50          
    100          
731              
732             UV
733             cube_cell(self, x, y, z, level)
734             SV *self
735             NV x
736             NV y
737             NV z
738             IV level
739             PREINIT:
740 41308 50         EXTRACT(self);
    50          
    50          
741             double dir[3];
742             CODE:
743             (void)h;
744 41308 100         if (level < 0 || level > SPH_CUBE_MAX_LEVEL) croak("cube_cell: level must be in 0..%d", SPH_CUBE_MAX_LEVEL);
    100          
745 41306           dir[0] = x; dir[1] = y; dir[2] = z;
746 41306           RETVAL = (UV)sph_cube_cell(dir, (int)level);
747             OUTPUT:
748             RETVAL
749              
750             UV
751             cube_cell_geo(self, lat, lon, level)
752             SV *self
753             NV lat
754             NV lon
755             IV level
756             PREINIT:
757 303 50         EXTRACT(self);
    50          
    50          
758             double dir[3];
759             CODE:
760             (void)h;
761 303 100         if (level < 0 || level > SPH_CUBE_MAX_LEVEL) croak("cube_cell_geo: level must be in 0..%d", SPH_CUBE_MAX_LEVEL);
    100          
762 301           sph_geo_to_xyz(1.0, lat, lon, 0.0, dir); /* unit direction */
763 301           RETVAL = (UV)sph_cube_cell(dir, (int)level);
764             OUTPUT:
765             RETVAL
766              
767             IV
768             cube_level(self, cell)
769             SV *self
770             UV cell
771             PREINIT:
772 20002 50         EXTRACT(self);
    50          
    50          
773             CODE:
774             (void)h;
775 20002 100         if (!sph_cube_valid((uint64_t)cell)) croak("cube_level: not a valid cube cell id");
776 20001 100         RETVAL = (IV)sph_cube_level((uint64_t)cell);
777             OUTPUT:
778             RETVAL
779              
780             void
781             cube_center(self, cell)
782             SV *self
783             UV cell
784             PREINIT:
785 39003 50         EXTRACT(self);
    50          
    50          
786             double d[3];
787             PPCODE:
788             (void)h;
789 39003 100         if (!sph_cube_valid((uint64_t)cell)) croak("cube_center: not a valid cube cell id");
790 39002           sph_cube_center((uint64_t)cell, d);
791 39002 50         EXTEND(SP, 3);
792 39002           PUSHs(sv_2mortal(newSVnv(d[0])));
793 39002           PUSHs(sv_2mortal(newSVnv(d[1])));
794 39002           PUSHs(sv_2mortal(newSVnv(d[2])));
795              
796             void
797             cube_center_geo(self, cell)
798             SV *self
799             UV cell
800             PREINIT:
801 2 50         EXTRACT(self);
    50          
    50          
802             double d[3], lat, lon, alt;
803             PPCODE:
804             (void)h;
805 2 100         if (!sph_cube_valid((uint64_t)cell)) croak("cube_center_geo: not a valid cube cell id");
806 1           sph_cube_center((uint64_t)cell, d);
807 1           sph_geo_of_xyz(1.0, d, &lat, &lon, &alt);
808 1 50         EXTEND(SP, 2);
809 1           PUSHs(sv_2mortal(newSVnv(lat)));
810 1           PUSHs(sv_2mortal(newSVnv(lon)));
811              
812             SV *
813             cube_parent(self, cell)
814             SV *self
815             UV cell
816             PREINIT:
817 8002 50         EXTRACT(self);
    50          
    50          
818             uint64_t p;
819             CODE:
820             (void)h;
821 8002 100         if (!sph_cube_valid((uint64_t)cell)) croak("cube_parent: not a valid cube cell id");
822 8001 100         RETVAL = sph_cube_parent((uint64_t)cell, &p) ? newSVuv((UV)p) : &PL_sv_undef;
823             OUTPUT:
824             RETVAL
825              
826             void
827             cube_children(self, cell)
828             SV *self
829             UV cell
830             PREINIT:
831 2002 50         EXTRACT(self);
    50          
    50          
832             uint64_t kids[4];
833             PPCODE:
834             (void)h;
835 2002 100         if (!sph_cube_valid((uint64_t)cell)) croak("cube_children: not a valid cube cell id");
836 2001 100         if (sph_cube_children((uint64_t)cell, kids)) {
837 2000 50         EXTEND(SP, 4);
838 10000 100         for (int k = 0; k < 4; k++) PUSHs(sv_2mortal(newSVuv((UV)kids[k])));
839             }
840              
841             void
842             cube_neighbors(self, cell)
843             SV *self
844             UV cell
845             PREINIT:
846 30121 50         EXTRACT(self);
    50          
    50          
847             uint64_t nb[4];
848             PPCODE:
849             (void)h;
850 30121 100         if (!sph_cube_valid((uint64_t)cell)) croak("cube_neighbors: not a valid cube cell id");
851 30120           sph_cube_neighbors((uint64_t)cell, nb);
852 30120 50         EXTEND(SP, 4);
853 150600 100         for (int k = 0; k < 4; k++) PUSHs(sv_2mortal(newSVuv((UV)nb[k])));
854              
855             void query_knn(self, ...)
856             SV *self
857             PREINIT:
858 57 50         EXTRACT(self);
    50          
    50          
859             PPCODE:
860 57           double c[3] = {0,0,0}; uint32_t k; int dims;
861 57 100         if (items == 4) { dims = 2; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); k=(uint32_t)SvUV(ST(3)); }
862 25 50         else if (items == 5) { dims = 3; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); c[2]=SvNV(ST(3)); k=(uint32_t)SvUV(ST(4)); }
863 0           else croak("query_knn: (x,y,k) or (x,y,z,k)");
864 57 100         if (k == 0) croak("query_knn: k must be >= 1");
865 349 50         EMIT_QUERY(sph_query_knn(h, c, k, dims, &col));
    50          
    50          
    50          
    100          
866              
867             void each_in_radius(self, ...)
868             SV *self
869             PREINIT:
870 7 50         EXTRACT(self);
    50          
    50          
871             PPCODE:
872             /* items: self,x,y,r,cb (5)=2D ; self,x,y,z,r,cb (6)=3D. cb is last. */
873 7           double c[3] = {0,0,0}, r; int dims; SV *cb;
874 7 100         if (items == 5) { dims=2; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); r=SvNV(ST(3)); cb=ST(4); }
875 1 50         else if (items == 6) { dims=3; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); c[2]=SvNV(ST(3)); r=SvNV(ST(4)); cb=ST(5); }
876 0           else croak("each_in_radius: (x,y,r,cb) or (x,y,z,r,cb)");
877 7 50         if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV) croak("each_in_radius: last arg must be a coderef");
    50          
878 7 50         if (r < 0 || !isfinite(r)) croak("each_in_radius: r must be a finite number >= 0");
    50          
879             /* snapshot under lock */
880 7           sph_collect_t col = { NULL, 0, 0 };
881 7           sph_rwlock_rdlock(h);
882 7           int rc = sph_query_radius(h, c, r, dims, &col);
883 7           sph_rwlock_rdunlock(h);
884 7 50         if (rc == SPH_Q_OOM) { free(col.vals); croak("each_in_radius: out of memory"); }
885 7 50         if (rc == SPH_Q_TOOBIG) { free(col.vals); croak(SPH_TOOBIG_MSG, (unsigned)SPH_MAX_QUERY_CELLS); }
886             /* invoke callback per value AFTER releasing the lock; G_EVAL so a die in
887             * the callback does not skip free(col.vals) -- re-throw after cleanup. */
888 38 100         for (size_t i = 0; i < col.n; i++) {
889 32 50         dSP; ENTER; SAVETMPS; PUSHMARK(SP);
890 32 50         XPUSHs(sv_2mortal(newSViv((IV)col.vals[i])));
891 32           PUTBACK;
892 32           call_sv(cb, G_VOID|G_DISCARD|G_EVAL);
893 32 50         FREETMPS; LEAVE;
894 32 50         if (SvTRUE(ERRSV)) { free(col.vals); croak_sv(ERRSV); }
    100          
    50          
895             }
896 6           free(col.vals);
897              
898             void
899             each_pair_within(self, max_r, cb)
900             SV *self
901             NV max_r
902             SV *cb
903             PREINIT:
904 6 50         EXTRACT(self);
    50          
    50          
905             PPCODE:
906 6 50         if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV) croak("each_pair_within: last arg must be a coderef");
    50          
907 6 50         if (max_r < 0 || !isfinite(max_r)) croak("each_pair_within: max_r must be a finite number >= 0");
    50          
908 1320 50         EMIT_PAIRS(sph_pairs(h, (double)max_r, sph_pair_to_collect, &col));
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    100          
909              
910             void
911             each_colliding_pair(self, cb)
912             SV *self
913             SV *cb
914             PREINIT:
915 4 50         EXTRACT(self);
    50          
    50          
916             PPCODE:
917 4 50         if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV) croak("each_colliding_pair: arg must be a coderef");
    50          
918 3337 50         EMIT_PAIRS(sph_pairs(h, -1.0, sph_pair_to_collect, &col));
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    0          
    100          
919              
920             void clear(self)
921             SV *self
922             PREINIT:
923 1 50         EXTRACT(self);
    50          
    50          
924             CODE:
925 1           sph_rwlock_wrlock(h);
926 1           sph_clear_locked(h);
927 1           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
928 1           sph_rwlock_wrunlock(h);
929              
930             SV *stats(self)
931             SV *self
932             PREINIT:
933 7 50         EXTRACT(self);
    50          
    50          
934             CODE:
935 7           sph_rwlock_rdlock(h);
936 7           uint32_t occ, mx, mxcell; sph_chain_stats(h, &occ, &mx, &mxcell);
937 7           uint32_t cnt = h->hdr->count, me = h->hdr->max_entries, nb = h->hdr->num_buckets;
938 7           sph_rwlock_rdunlock(h);
939 7           HV *hv = newHV();
940 7           hv_store(hv, "count", 5, newSVuv(cnt), 0);
941 7           hv_store(hv, "max_entries", 11, newSVuv(me), 0);
942 7           hv_store(hv, "num_buckets", 11, newSVuv(nb), 0);
943 7           hv_store(hv, "cell_size", 9, newSVnv(h->hdr->cell_size), 0);
944 7           hv_store(hv, "free_slots", 10, newSVuv(me - cnt), 0);
945 7           hv_store(hv, "occupied_buckets", 16, newSVuv(occ), 0);
946 7           hv_store(hv, "max_chain", 9, newSVuv(mx), 0);
947 7           hv_store(hv, "max_cell", 8, newSVuv(mxcell), 0);
948 7 50         hv_store(hv, "load_factor", 11, newSVnv(nb ? (double)cnt / nb : 0), 0);
949 7           hv_store(hv, "ops", 3, newSVuv((UV)__atomic_load_n(&h->hdr->stat_ops, __ATOMIC_RELAXED)), 0);
950 7           hv_store(hv, "mmap_size", 9, newSVuv((UV)h->mmap_size), 0);
951 7           RETVAL = newRV_noinc((SV *)hv);
952             OUTPUT: RETVAL