From 4b71083faec1bd2d5533d882a2a7d54d8a1f6a41 Mon Sep 17 00:00:00 2001 From: tidwall Date: Wed, 21 Sep 2022 10:40:37 -0700 Subject: [PATCH] Binary objects --- internal/object/object.go | 125 ++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 20 deletions(-) diff --git a/internal/object/object.go b/internal/object/object.go index 4fc019ea..f84ccb45 100644 --- a/internal/object/object.go +++ b/internal/object/object.go @@ -1,23 +1,73 @@ package object import ( + "encoding/binary" + "unsafe" + "github.com/tidwall/geojson" "github.com/tidwall/geojson/geometry" "github.com/tidwall/tile38/internal/field" ) +type pointObject struct { + base Object + pt geojson.SimplePoint +} + +type geoObject struct { + base Object + geo geojson.Object +} + +const opoint = 1 +const ogeo = 2 + type Object struct { - id string - geo geojson.Object - expires int64 // unix nano expiration - fields field.List + head string // tuple (kind,expires,id) + fields field.List +} + +func (o *Object) geo() geojson.Object { + if o != nil { + switch o.head[0] { + case opoint: + return &(*pointObject)(unsafe.Pointer(o)).pt + case ogeo: + return (*geoObject)(unsafe.Pointer(o)).geo + } + } + return nil +} + +// uvarint is a slightly modified version of binary.Uvarint, and it's a little +// faster. But it lacks overflow checks which are not needed for our use. +func uvarint(s string) (uint64, int) { + var x uint64 + for i := 0; i < len(s); i++ { + b := s[i] + if b < 0x80 { + return x | uint64(b)<<(i*7), i + 1 + } + x |= uint64(b&0x7f) << (i * 7) + } + return 0, 0 +} + +func varint(s string) (int64, int) { + ux, n := uvarint(s) + x := int64(ux >> 1) + if ux&1 != 0 { + x = ^x + } + return x, n } func (o *Object) ID() string { if o == nil { return "" } - return o.id + _, n := varint(o.head[1:]) + return o.head[1+n:] } func (o *Object) Fields() field.List { @@ -31,32 +81,32 @@ func (o *Object) Expires() int64 { if o == nil { return 0 } - return o.expires + ex, _ := varint(o.head[1:]) + return ex } func (o *Object) Rect() geometry.Rect { - if o == nil || o.geo == nil { + ogeo := o.geo() + if ogeo == nil { return geometry.Rect{} } - return o.geo.Rect() + return ogeo.Rect() } func (o *Object) Geo() geojson.Object { - if o == nil || o.geo == nil { - return nil - } - return o.geo + return o.geo() } func (o *Object) String() string { - if o == nil || o.geo == nil { + ogeo := o.geo() + if ogeo == nil { return "" } - return o.geo.String() + return ogeo.String() } func (o *Object) IsSpatial() bool { - _, ok := o.geo.(geojson.Spatial) + _, ok := o.geo().(geojson.Spatial) return ok } @@ -75,12 +125,47 @@ func (o *Object) Weight() int { return weight } +func makeHead(kind byte, id string, expires int64) string { + var exb [20]byte + exn := binary.PutVarint(exb[:], expires) + n := 1 + exn + len(id) + head := make([]byte, n) + head[0] = kind + copy(head[1:], exb[:exn]) + copy(head[1+exn:], id) + return *(*string)(unsafe.Pointer(&head)) +} + +func newPoint(id string, pt geometry.Point, expires int64, fields field.List, +) *Object { + return (*Object)(unsafe.Pointer(&pointObject{ + Object{ + head: makeHead(opoint, id, expires), + fields: fields, + }, + geojson.SimplePoint{Point: pt}, + })) +} +func newGeo(id string, geo geojson.Object, expires int64, fields field.List, +) *Object { + return (*Object)(unsafe.Pointer(&geoObject{ + Object{ + head: makeHead(ogeo, id, expires), + fields: fields, + }, + geo, + })) +} + func New(id string, geo geojson.Object, expires int64, fields field.List, ) *Object { - return &Object{ - id: id, - geo: geo, - expires: expires, - fields: fields, + switch p := geo.(type) { + case *geojson.SimplePoint: + return newPoint(id, p.Base(), expires, fields) + case *geojson.Point: + if p.IsSimple() { + return newPoint(id, p.Base(), expires, fields) + } } + return newGeo(id, geo, expires, fields) }