Data-SpatialHash-Shared

 view release on metacpan or  search on metacpan

Shared.xs  view on Meta::CPAN

    RETVAL = h->notify_fd;
  OUTPUT:
    RETVAL

bool
notify(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = sph_notify(h);
  OUTPUT:
    RETVAL

SV *
eventfd_consume(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    int64_t n = sph_eventfd_consume(h);
    RETVAL = (n >= 0) ? newSViv((IV)n) : &PL_sv_undef;
  OUTPUT:
    RETVAL

UV
max_entries(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->hdr->max_entries;
  OUTPUT:
    RETVAL

UV
num_buckets(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->hdr->num_buckets;
  OUTPUT:
    RETVAL

NV
cell_size(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->hdr->cell_size;
  OUTPUT:
    RETVAL

void
world(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  PPCODE:
    if (h->wrap) {
        int dims = (h->hdr->world[2] > 0.0) ? 3 : 2;
        EXTEND(SP, dims);
        for (int i = 0; i < dims; i++) PUSHs(sv_2mortal(newSVnv(h->hdr->world[i])));
    }

NV
sphere(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->hdr->sphere_radius;
  OUTPUT:
    RETVAL

UV
count(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = __atomic_load_n(&h->hdr->count, __ATOMIC_ACQUIRE);
  OUTPUT:
    RETVAL

SV *
path(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->path ? newSVpv(h->path, 0) : &PL_sv_undef;
  OUTPUT:
    RETVAL

SV *
insert(self, x, y, ...)
    SV *self
    NV x
    NV y
  PREINIT:
    EXTRACT(self);
    double z, radius;
    int64_t val;
    uint32_t idx;
  CODE:
    /* (x,y,value)=4 2D ; (x,y,z,value)=5 3D ; (x,y,z,value,radius)=6 3D+radius */
    z = 0; radius = 0;
    if (items == 4) { val = (int64_t)SvIV(ST(3)); }
    else if (items == 5) { z = (double)SvNV(ST(3)); val = (int64_t)SvIV(ST(4)); }
    else if (items == 6) { z = (double)SvNV(ST(3)); val = (int64_t)SvIV(ST(4)); radius = (double)SvNV(ST(5)); }
    else croak("insert: expected (x,y,value), (x,y,z,value), or (x,y,z,value,radius)");
    if (radius < 0 || !isfinite(radius)) croak("insert: radius must be a finite number >= 0");
    REEXTRACT(self);
    sph_rwlock_wrlock(h);
    idx = sph_insert_locked(h, x, y, z, val, radius);
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    sph_rwlock_wrunlock(h);
    RETVAL = (idx == SPH_NONE) ? &PL_sv_undef : newSVuv(idx);

Shared.xs  view on Meta::CPAN

    sph_rwlock_rdunlock(h);
  OUTPUT:
    RETVAL

IV
move_many(self, rows)
    SV *self
    SV *rows
  PREINIT:
    EXTRACT(self);
    IV moved;
  CODE:
    SvGETMAGIC(rows);
    if (!SvROK(rows) || SvTYPE(SvRV(rows)) != SVt_PVAV)
        croak("move_many: expected an arrayref of [handle,x,y] or [handle,x,y,z]");
    {
    AV *av = (AV *)SvRV(rows);
    SSize_t nr = av_len(av) + 1;
    struct mm_row { uint32_t handle; double x, y, z; int valid; } *R = NULL;
    moved = 0;
    if (nr > 0) { Newxz(R, nr, struct mm_row); SAVEFREEPV(R); }
    /* Phase 1: resolve all user-controlled conversions with NO lock held.
       SvUV/SvNV run get-magic/overload = arbitrary Perl that may die; doing it
       here (lock-free) means a longjmp can't strand the write lock. */
    for (SSize_t i = 0; i < nr; i++) {
        SV **rv = av_fetch(av, i, 0);
        if (rv) SvGETMAGIC(*rv);   /* a tied-array element is a deferred-magic PVLV */
        if (!rv || !SvROK(*rv) || SvTYPE(SvRV(*rv)) != SVt_PVAV) continue;
        AV *row = (AV *)SvRV(*rv);
        SSize_t rl = av_len(row) + 1;
        if (rl != 3 && rl != 4) continue;
        SV **hp = av_fetch(row, 0, 0), **xp = av_fetch(row, 1, 0), **yp = av_fetch(row, 2, 0);
        SV **zp = (rl == 4) ? av_fetch(row, 3, 0) : NULL;
        if (!hp || !xp || !yp) continue;
        R[i].handle = sph_idx(SvUV(*hp));
        R[i].x = SvNV(*xp);
        R[i].y = SvNV(*yp);
        R[i].z = zp ? SvNV(*zp) : 0.0;
        R[i].valid = 1;
    }
    /* Phase 2: pure C under the write lock, using pre-resolved values only. */
    REEXTRACT(self);
    sph_rwlock_wrlock(h);
    for (SSize_t i = 0; i < nr; i++) {
        if (!R[i].valid) continue;
        if (sph_move_locked(h, R[i].handle, R[i].x, R[i].y, R[i].z)) moved++;
    }
    if (nr > 0) __atomic_fetch_add(&h->hdr->stat_ops, (uint64_t)nr, __ATOMIC_RELAXED);
    sph_rwlock_wrunlock(h);
    }
    RETVAL = moved;
  OUTPUT:
    RETVAL

void
insert_many(self, rows)
    SV *self
    SV *rows
  PREINIT:
    EXTRACT(self);
  PPCODE:
    SvGETMAGIC(rows);
    if (!SvROK(rows) || SvTYPE(SvRV(rows)) != SVt_PVAV)
        croak("insert_many: expected an arrayref of [x,y,value] or [x,y,value,radius]");
    {
    AV *av = (AV *)SvRV(rows);
    SSize_t nr = av_len(av) + 1;
    struct im_row { double x, y, rad; int64_t val; int valid; } *R = NULL;
    uint32_t *ids = NULL;
    EXTEND(SP, nr);
    if (nr > 0) {
        Newxz(R, nr, struct im_row);   SAVEFREEPV(R);
        Newx(ids, nr, uint32_t);       SAVEFREEPV(ids);
    }
    /* Phase 1: resolve all user-controlled conversions with NO lock held.
       SvNV/SvIV run get-magic/overload = arbitrary Perl that may die; doing it
       here (lock-free) means a longjmp can't strand the write lock. */
    for (SSize_t i = 0; i < nr; i++) {
        SV **rv = av_fetch(av, i, 0);
        if (rv) SvGETMAGIC(*rv);   /* a tied-array element is a deferred-magic PVLV */
        if (rv && SvROK(*rv) && SvTYPE(SvRV(*rv)) == SVt_PVAV) {
            AV *row = (AV *)SvRV(*rv);
            SSize_t rl = av_len(row) + 1;
            if (rl == 3 || rl == 4) {
                SV **xp = av_fetch(row, 0, 0), **yp = av_fetch(row, 1, 0), **vp = av_fetch(row, 2, 0);
                SV **rp = (rl == 4) ? av_fetch(row, 3, 0) : NULL;
                if (xp && yp && vp) {
                    double rad = rp ? SvNV(*rp) : 0.0;
                    /* a row with a bad radius yields an undef handle, like other
                       malformed rows */
                    if (rad >= 0 && isfinite(rad)) {
                        R[i].x = SvNV(*xp);
                        R[i].y = SvNV(*yp);
                        R[i].val = (int64_t)SvIV(*vp);
                        R[i].rad = rad;
                        R[i].valid = 1;
                    }
                }
            }
        }
    }
    /* Phase 2: pure C under the write lock into a C result buffer. */
    REEXTRACT(self);
    sph_rwlock_wrlock(h);
    for (SSize_t i = 0; i < nr; i++)
        ids[i] = R[i].valid
            ? sph_insert_locked(h, R[i].x, R[i].y, 0.0, R[i].val, R[i].rad)
            : SPH_NONE;
    if (nr > 0) __atomic_fetch_add(&h->hdr->stat_ops, (uint64_t)nr, __ATOMIC_RELAXED);
    sph_rwlock_wrunlock(h);
    /* Phase 3: build result SVs after the lock is released. */
    for (SSize_t i = 0; i < nr; i++)
        PUSHs(ids[i] == SPH_NONE ? &PL_sv_undef : sv_2mortal(newSVuv(ids[i])));
    }

void
position(self, handle)
    SV *self
    UV handle
  PREINIT:
    EXTRACT(self);
    double px, py, pz;
  PPCODE:
    sph_rwlock_rdlock(h);
    REQUIRE_LIVE_RD(h, sph_idx(handle));
    px = h->entries[sph_idx(handle)].pos[0];
    py = h->entries[sph_idx(handle)].pos[1];
    pz = h->entries[sph_idx(handle)].pos[2];
    sph_rwlock_rdunlock(h);
    EXTEND(SP, 3);
    PUSHs(sv_2mortal(newSVnv(px)));
    PUSHs(sv_2mortal(newSVnv(py)));
    PUSHs(sv_2mortal(newSVnv(pz)));

void
query_cell(self, x, y, ...)
    SV *self
    NV x
    NV y
  PREINIT:
    EXTRACT(self);
  PPCODE:
    if (items != 3 && items != 4) croak("query_cell: (x,y) or (x,y,z)");
    int dims = (items == 4) ? 3 : 2;
    double p[3] = { x, y, dims==3 ? (double)SvNV(ST(3)) : 0 };
    REEXTRACT(self);
    EMIT_QUERY(sph_query_cell(h, p, dims, &col));

void
query_aabb(self, ...)
    SV *self
  PREINIT:
    EXTRACT(self);
  PPCODE:
    double lo[3], hi[3]; int dims;
    if (items == 5) { dims = 2;
        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;
    } else if (items == 7) { dims = 3;
        lo[0]=SvNV(ST(1)); lo[1]=SvNV(ST(2)); lo[2]=SvNV(ST(3));
        hi[0]=SvNV(ST(4)); hi[1]=SvNV(ST(5)); hi[2]=SvNV(ST(6));
    } else croak("query_aabb: (x0,y0,x1,y1) or (x0,y0,z0,x1,y1,z1)");
    REEXTRACT(self);
    EMIT_QUERY(sph_query_aabb(h, lo, hi, dims, &col));

void
query_radius(self, ...)
    SV *self
  PREINIT:
    EXTRACT(self);
  PPCODE:
    double c[3] = {0,0,0}, r; int dims;
    if (items == 4) { dims = 2; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); r=SvNV(ST(3)); }
    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)); }
    else croak("query_radius: (x,y,r) or (x,y,z,r)");
    if (r < 0 || !isfinite(r)) croak("query_radius: r must be a finite number >= 0");
    REEXTRACT(self);
    EMIT_QUERY(sph_query_radius(h, c, r, dims, &col));

# Batched broad-phase: N radius queries under ONE read lock. Returns an arrayref of
# id-list arrayrefs, one per query in input order (each == [query_radius(@$q)]). A
# malformed row (not a 3/4-elem arrayref, or a negative/non-finite r) yields an empty
# list for that slot -- can't croak under the lock, mirrors insert_many. OOM/TOOBIG
# from any query croak after freeing the partial result tree.
SV *
query_radius_many(self, queries)
    SV *self
    SV *queries
  PREINIT:
    EXTRACT(self);
  CODE:
    SvGETMAGIC(queries);
    if (!SvROK(queries) || SvTYPE(SvRV(queries)) != SVt_PVAV)
        croak("query_radius_many: expected an arrayref of [x,y,r] or [x,y,z,r]");
    {
    AV *qav = (AV *)SvRV(queries);
    SSize_t nq = av_len(qav) + 1;
    AV *out = newAV();
    if (nq > 0) av_extend(out, nq - 1);
    int err = 0;                                  /* 0 ok, 1 OOM, 2 TOOBIG */
    struct qr_row { double c[3]; double r; int dims; } *Q = NULL;
    if (nq > 0) { Newxz(Q, nq, struct qr_row); SAVEFREEPV(Q); }
    /* Phase 1: resolve all user-controlled conversions with NO lock held.
       SvNV runs get-magic/overload = arbitrary Perl that may die; doing it here
       (lock-free) means a longjmp can't strand the read lock. dims stays 0 for a
       malformed query -> an empty result slot. */
    for (SSize_t i = 0; i < nq; i++) {
        SV **qp = av_fetch(qav, i, 0);
        if (qp) SvGETMAGIC(*qp);   /* a tied-array element is a deferred-magic PVLV */
        if (qp && SvROK(*qp) && SvTYPE(SvRV(*qp)) == SVt_PVAV) {
            AV *q = (AV *)SvRV(*qp);
            SSize_t ql = av_len(q) + 1;
            double c[3] = {0,0,0}, r = 0; int dims = 0;
            if (ql == 3) {
                SV **x=av_fetch(q,0,0), **y=av_fetch(q,1,0), **rr=av_fetch(q,2,0);
                if (x && y && rr) { dims=2; c[0]=SvNV(*x); c[1]=SvNV(*y); r=SvNV(*rr); }
            } else if (ql == 4) {
                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);
                if (x && y && z && rr) { dims=3; c[0]=SvNV(*x); c[1]=SvNV(*y); c[2]=SvNV(*z); r=SvNV(*rr); }
            }
            if (dims && r >= 0 && isfinite(r)) {
                Q[i].c[0]=c[0]; Q[i].c[1]=c[1]; Q[i].c[2]=c[2]; Q[i].r=r; Q[i].dims=dims;
            }
        }
    }
    /* Phase 2: run the queries under one read lock, collecting raw ids into C
       buffers only.  Building the Perl result tree here (newAV/newSViv/av_push)
       could hit an allocation failure -> Perl longjmps out with the read lock
       still held, permanently stranding this process's rdepth (a later writer
       then hangs forever draining it).  So we defer ALL SV building to Phase 3,
       after rdunlock -- matching EMIT_QUERY and every sibling query method. */

Shared.xs  view on Meta::CPAN

            av_extend(res, (SSize_t)R[i].n - 1);
            for (size_t k = 0; k < R[i].n; k++) av_push(res, newSViv((IV)R[i].vals[k]));
        }
        free(R[i].vals);                           /* malloc'd by sph_query_radius */
        av_push(out, newRV_noinc((SV *)res));      /* out owns res */
    }
    RETVAL = newRV_noinc((SV *)out);
    }
  OUTPUT:
    RETVAL

SV *
insert_geo(self, lat, lon, alt, value)
    SV *self
    NV lat
    NV lon
    NV alt
    IV value
  PREINIT:
    EXTRACT(self);
    double xyz[3];
    uint32_t idx;
  CODE:
    if (!(h->hdr->sphere_radius > 0.0)) croak("insert_geo: map was not created with sphere => R");
    sph_geo_to_xyz(h->hdr->sphere_radius, lat, lon, alt, xyz);
    sph_rwlock_wrlock(h);
    idx = sph_insert_locked(h, xyz[0], xyz[1], xyz[2], (int64_t)value, 0.0);
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    sph_rwlock_wrunlock(h);
    RETVAL = (idx == SPH_NONE) ? &PL_sv_undef : newSVuv(idx);
  OUTPUT:
    RETVAL

bool
move_geo(self, handle, lat, lon, alt)
    SV *self
    UV handle
    NV lat
    NV lon
    NV alt
  PREINIT:
    EXTRACT(self);
    double xyz[3];
  CODE:
    if (!(h->hdr->sphere_radius > 0.0)) croak("move_geo: map was not created with sphere => R");
    sph_geo_to_xyz(h->hdr->sphere_radius, lat, lon, alt, xyz);
    sph_rwlock_wrlock(h);
    RETVAL = sph_move_locked(h, sph_idx(handle), xyz[0], xyz[1], xyz[2]);
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    sph_rwlock_wrunlock(h);
  OUTPUT:
    RETVAL

void
position_geo(self, handle)
    SV *self
    UV handle
  PREINIT:
    EXTRACT(self);
    double p[3], lat, lon, alt;
  PPCODE:
    if (!(h->hdr->sphere_radius > 0.0)) croak("position_geo: map was not created with sphere => R");
    sph_rwlock_rdlock(h);
    REQUIRE_LIVE_RD(h, sph_idx(handle));
    p[0] = h->entries[sph_idx(handle)].pos[0];
    p[1] = h->entries[sph_idx(handle)].pos[1];
    p[2] = h->entries[sph_idx(handle)].pos[2];
    sph_rwlock_rdunlock(h);
    sph_geo_of_xyz(h->hdr->sphere_radius, p, &lat, &lon, &alt);
    EXTEND(SP, 3);
    PUSHs(sv_2mortal(newSVnv(lat)));
    PUSHs(sv_2mortal(newSVnv(lon)));
    PUSHs(sv_2mortal(newSVnv(alt)));

void
query_geo_radius(self, lat, lon, alt, dist)
    SV *self
    NV lat
    NV lon
    NV alt
    NV dist
  PREINIT:
    EXTRACT(self);
    double c[3];
  PPCODE:
    if (!(h->hdr->sphere_radius > 0.0)) croak("query_geo_radius: map was not created with sphere => R");
    if (dist < 0 || !isfinite(dist)) croak("query_geo_radius: dist must be a finite number >= 0");
    sph_geo_to_xyz(h->hdr->sphere_radius, lat, lon, alt, c);
    EMIT_QUERY(sph_query_radius(h, c, (double)dist, 3, &col));

UV
cube_cell(self, x, y, z, level)
    SV *self
    NV x
    NV y
    NV z
    IV level
  PREINIT:
    EXTRACT(self);
    double dir[3];
  CODE:
    (void)h;
    if (level < 0 || level > SPH_CUBE_MAX_LEVEL) croak("cube_cell: level must be in 0..%d", SPH_CUBE_MAX_LEVEL);
    dir[0] = x; dir[1] = y; dir[2] = z;
    RETVAL = (UV)sph_cube_cell(dir, (int)level);
  OUTPUT:
    RETVAL

UV
cube_cell_geo(self, lat, lon, level)
    SV *self
    NV lat
    NV lon
    IV level
  PREINIT:
    EXTRACT(self);
    double dir[3];
  CODE:
    (void)h;
    if (level < 0 || level > SPH_CUBE_MAX_LEVEL) croak("cube_cell_geo: level must be in 0..%d", SPH_CUBE_MAX_LEVEL);
    sph_geo_to_xyz(1.0, lat, lon, 0.0, dir);   /* unit direction */
    RETVAL = (UV)sph_cube_cell(dir, (int)level);
  OUTPUT:
    RETVAL

IV
cube_level(self, cell)
    SV *self
    UV cell
  PREINIT:
    EXTRACT(self);
  CODE:
    (void)h;
    if (!sph_cube_valid((uint64_t)cell)) croak("cube_level: not a valid cube cell id");
    RETVAL = (IV)sph_cube_level((uint64_t)cell);
  OUTPUT:
    RETVAL

void
cube_center(self, cell)
    SV *self
    UV cell
  PREINIT:
    EXTRACT(self);
    double d[3];
  PPCODE:
    (void)h;
    if (!sph_cube_valid((uint64_t)cell)) croak("cube_center: not a valid cube cell id");
    sph_cube_center((uint64_t)cell, d);
    EXTEND(SP, 3);
    PUSHs(sv_2mortal(newSVnv(d[0])));
    PUSHs(sv_2mortal(newSVnv(d[1])));
    PUSHs(sv_2mortal(newSVnv(d[2])));

void
cube_center_geo(self, cell)
    SV *self
    UV cell
  PREINIT:
    EXTRACT(self);
    double d[3], lat, lon, alt;
  PPCODE:
    (void)h;
    if (!sph_cube_valid((uint64_t)cell)) croak("cube_center_geo: not a valid cube cell id");
    sph_cube_center((uint64_t)cell, d);
    sph_geo_of_xyz(1.0, d, &lat, &lon, &alt);
    EXTEND(SP, 2);
    PUSHs(sv_2mortal(newSVnv(lat)));
    PUSHs(sv_2mortal(newSVnv(lon)));

SV *
cube_parent(self, cell)
    SV *self
    UV cell
  PREINIT:
    EXTRACT(self);
    uint64_t p;
  CODE:
    (void)h;
    if (!sph_cube_valid((uint64_t)cell)) croak("cube_parent: not a valid cube cell id");
    RETVAL = sph_cube_parent((uint64_t)cell, &p) ? newSVuv((UV)p) : &PL_sv_undef;
  OUTPUT:
    RETVAL

void
cube_children(self, cell)
    SV *self
    UV cell
  PREINIT:
    EXTRACT(self);
    uint64_t kids[4];
  PPCODE:
    (void)h;
    if (!sph_cube_valid((uint64_t)cell)) croak("cube_children: not a valid cube cell id");
    if (sph_cube_children((uint64_t)cell, kids)) {
        EXTEND(SP, 4);
        for (int k = 0; k < 4; k++) PUSHs(sv_2mortal(newSVuv((UV)kids[k])));
    }

void
cube_neighbors(self, cell)
    SV *self
    UV cell
  PREINIT:
    EXTRACT(self);
    uint64_t nb[4];
  PPCODE:
    (void)h;
    if (!sph_cube_valid((uint64_t)cell)) croak("cube_neighbors: not a valid cube cell id");
    sph_cube_neighbors((uint64_t)cell, nb);
    EXTEND(SP, 4);
    for (int k = 0; k < 4; k++) PUSHs(sv_2mortal(newSVuv((UV)nb[k])));

void query_knn(self, ...)
    SV *self
  PREINIT:
    EXTRACT(self);
  PPCODE:
    double c[3] = {0,0,0}; uint32_t k; int dims; IV kiv;
    if (items == 4) { dims = 2; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); kiv=SvIV(ST(3)); }
    else if (items == 5) { dims = 3; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); c[2]=SvNV(ST(3)); kiv=SvIV(ST(4)); }
    else croak("query_knn: (x,y,k) or (x,y,z,k)");
    if (kiv < 1) croak("query_knn: k must be >= 1");   /* SvIV so a negative k is caught (SvUV would wrap it to a huge k) */
    k = kiv > (IV)UINT32_MAX ? UINT32_MAX : (uint32_t)kiv;   /* clamp an absurd k; knn caps at count anyway */
    REEXTRACT(self);
    EMIT_QUERY(sph_query_knn(h, c, k, dims, &col));

void each_in_radius(self, ...)
    SV *self
  PREINIT:
    EXTRACT(self);
  PPCODE:
    /* items: self,x,y,r,cb (5)=2D ; self,x,y,z,r,cb (6)=3D. cb is last. */
    double c[3] = {0,0,0}, r; int dims; SV *cb;
    if (items == 5) { dims=2; c[0]=SvNV(ST(1)); c[1]=SvNV(ST(2)); r=SvNV(ST(3)); cb=ST(4); }
    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); }
    else croak("each_in_radius: (x,y,r,cb) or (x,y,z,r,cb)");
    SvGETMAGIC(cb);   /* a tied/overloaded scalar may FETCH to a coderef */
    if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV) croak("each_in_radius: last arg must be a coderef");
    if (r < 0 || !isfinite(r)) croak("each_in_radius: r must be a finite number >= 0");
    /* snapshot under lock */
    sph_collect_t col = { NULL, 0, 0 };
    REEXTRACT(self);
    sph_rwlock_rdlock(h);
    int rc = sph_query_radius(h, c, r, dims, &col);
    sph_rwlock_rdunlock(h);
    if (rc == SPH_Q_OOM)    { free(col.vals); croak("each_in_radius: out of memory"); }
    if (rc == SPH_Q_TOOBIG) { free(col.vals); croak(SPH_TOOBIG_MSG, (unsigned)SPH_MAX_QUERY_CELLS); }
    /* invoke callback per value AFTER releasing the lock; G_EVAL so a die in
     * the callback does not skip free(col.vals) -- re-throw after cleanup. */
    for (size_t i = 0; i < col.n; i++) {
        dSP; ENTER; SAVETMPS; PUSHMARK(SP);
        XPUSHs(sv_2mortal(newSViv((IV)col.vals[i])));
        PUTBACK;
        call_sv(cb, G_VOID|G_DISCARD|G_EVAL);
        FREETMPS; LEAVE;
        if (SvTRUE(ERRSV)) { free(col.vals); croak_sv(ERRSV); }
    }
    free(col.vals);

void
each_pair_within(self, max_r, cb)
    SV *self
    NV max_r
    SV *cb
  PREINIT:
    EXTRACT(self);
  PPCODE:
    SvGETMAGIC(cb);   /* a tied/overloaded scalar may FETCH to a coderef */
    if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV) croak("each_pair_within: last arg must be a coderef");
    if (max_r < 0 || !isfinite(max_r)) croak("each_pair_within: max_r must be a finite number >= 0");
    REEXTRACT(self);
    EMIT_PAIRS(sph_pairs(h, (double)max_r, sph_pair_to_collect, &col));

void
each_colliding_pair(self, cb)
    SV *self
    SV *cb
  PREINIT:
    EXTRACT(self);
  PPCODE:
    SvGETMAGIC(cb);   /* a tied/overloaded scalar may FETCH to a coderef */
    if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV) croak("each_colliding_pair: arg must be a coderef");
    REEXTRACT(self);
    EMIT_PAIRS(sph_pairs(h, -1.0, sph_pair_to_collect, &col));

void clear(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    sph_rwlock_wrlock(h);
    sph_clear_locked(h);
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    sph_rwlock_wrunlock(h);

SV *stats(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    sph_rwlock_rdlock(h);
    uint32_t occ, mx, mxcell; sph_chain_stats(h, &occ, &mx, &mxcell);
    uint32_t cnt = h->hdr->count, me = h->hdr->max_entries, nb = h->hdr->num_buckets;
    sph_rwlock_rdunlock(h);
    HV *hv = newHV();
    hv_store(hv, "count", 5, newSVuv(cnt), 0);
    hv_store(hv, "max_entries", 11, newSVuv(me), 0);
    hv_store(hv, "num_buckets", 11, newSVuv(nb), 0);
    hv_store(hv, "cell_size", 9, newSVnv(h->hdr->cell_size), 0);
    hv_store(hv, "free_slots", 10, newSVuv(me - cnt), 0);
    hv_store(hv, "occupied_buckets", 16, newSVuv(occ), 0);
    hv_store(hv, "max_chain", 9, newSVuv(mx), 0);
    hv_store(hv, "max_cell", 8, newSVuv(mxcell), 0);
    hv_store(hv, "load_factor", 11, newSVnv(nb ? (double)cnt / nb : 0), 0);
    hv_store(hv, "ops", 3, newSVuv((UV)__atomic_load_n(&h->hdr->stat_ops, __ATOMIC_RELAXED)), 0);
    hv_store(hv, "mmap_size", 9, newSVuv((UV)h->mmap_size), 0);
    RETVAL = newRV_noinc((SV *)hv);
  OUTPUT: RETVAL



( run in 1.507 second using v1.01-cache-2.11-cpan-92ad3014f07 )