mirror of
https://github.com/tuneinsight/lattigo.git
synced 2025-09-13 03:27:14 +00:00
improved the Writer/Reader-based serialization
The WriterTo and ReaderFrom standard interface should be sufficient for the serialization of lattigo objects from their pointers. Other interfaces such as BinaryMarshaller should be based on WriterTo. This is possible in an efficient way if the Writer and Reader interface expose their internal buffer.
This commit is contained in:
@@ -2,7 +2,6 @@ package structs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -32,13 +31,17 @@ func (m Map[K, T]) CopyNew() *Map[K, T] {
|
||||
return &mcpy
|
||||
}
|
||||
|
||||
// WriteTo writes the object on an io.Writer.
|
||||
// To ensure optimal efficiency and minimal allocations, the user is encouraged
|
||||
// to provide a struct implementing the interface buffer.Writer, which defines
|
||||
// a subset of the method of the bufio.Writer.
|
||||
// If w is not compliant to the buffer.Writer interface, it will be wrapped in
|
||||
// a new bufio.Writer.
|
||||
// For additional information, see lattigo/utils/buffer/writer.go.
|
||||
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
|
||||
// interface, and will write exactly object.BinarySize() bytes on w.
|
||||
//
|
||||
// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go),
|
||||
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
|
||||
// is preferable to pass a buffer.Writer directly:
|
||||
//
|
||||
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
|
||||
// io.Writer in a pre-allocated bufio.Writer.
|
||||
// - When writing to a pre-allocated var b []byte, it is preferable to pass
|
||||
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
|
||||
func (m *Map[K, T]) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
if w, isWritable := any(new(T)).(io.WriterTo); !isWritable {
|
||||
@@ -78,13 +81,17 @@ func (m *Map[K, T]) WriteTo(w io.Writer) (n int64, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrom reads on the object from an io.Writer.
|
||||
// To ensure optimal efficiency and minimal allocations, the user is encouraged
|
||||
// to provide a struct implementing the interface buffer.Reader, which defines
|
||||
// a subset of the method of the bufio.Reader.
|
||||
// If r is not compliant to the buffer.Reader interface, it will be wrapped in
|
||||
// a new bufio.Reader.
|
||||
// For additional information, see lattigo/utils/buffer/reader.go.
|
||||
// ReadFrom reads on the object from an io.Writer. It implements the
|
||||
// io.ReaderFrom interface.
|
||||
//
|
||||
// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go),
|
||||
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
|
||||
// is preferable to pass a buffer.Reader directly:
|
||||
//
|
||||
// - When reading multiple values from a io.Reader, it is preferable to first
|
||||
// first wrap io.Reader in a pre-allocated bufio.Reader.
|
||||
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
|
||||
// as w (see lattigo/utils/buffer/buffer.go).
|
||||
func (m *Map[K, T]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
|
||||
if r, isReadable := any(new(T)).(io.ReaderFrom); !isReadable {
|
||||
@@ -113,7 +120,7 @@ func (m *Map[K, T]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
}
|
||||
n += int64(inc1)
|
||||
|
||||
var val *T = new(T)
|
||||
var val = new(T)
|
||||
var inc2 int64
|
||||
if inc2, err = any(val).(io.ReaderFrom).ReadFrom(r); err != nil {
|
||||
return n + inc2, err
|
||||
@@ -148,6 +155,20 @@ func (m Map[K, T]) BinarySize() (size int) {
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
|
||||
func (m *Map[K, T]) MarshalBinary() (p []byte, err error) {
|
||||
buf := buffer.NewBufferSize(m.BinarySize())
|
||||
_, err = m.WriteTo(buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes a slice of bytes generated by
|
||||
// MarshalBinary or WriteTo on the object.
|
||||
func (m *Map[K, T]) UnmarshalBinary(p []byte) (err error) {
|
||||
_, err = m.ReadFrom(buffer.NewBuffer(p))
|
||||
return
|
||||
}
|
||||
|
||||
// Encode encodes the object into a binary form on a preallocated slice of bytes
|
||||
// and returns the number of bytes written.
|
||||
func (m *Map[K, T]) Encode(p []byte) (n int, err error) {
|
||||
@@ -200,7 +221,7 @@ func (m *Map[K, T]) Decode(p []byte) (n int, err error) {
|
||||
n += 8
|
||||
|
||||
var inc int
|
||||
var val *T = new(T)
|
||||
var val = new(T)
|
||||
if inc, err = any(val).(Decoder).Decode(p[n:]); err != nil {
|
||||
return n + inc, err
|
||||
}
|
||||
@@ -210,17 +231,3 @@ func (m *Map[K, T]) Decode(p []byte) (n int, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
|
||||
func (m *Map[K, T]) MarshalBinary() (p []byte, err error) {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
_, err = m.WriteTo(buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes a slice of bytes generated by
|
||||
// MarshalBinary or WriteTo on the object.
|
||||
func (m *Map[K, T]) UnmarshalBinary(p []byte) (err error) {
|
||||
_, err = m.ReadFrom(bytes.NewBuffer(p))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package structs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -33,13 +32,33 @@ func (m Matrix[T]) CopyNew() *Matrix[T] {
|
||||
return &mcpy
|
||||
}
|
||||
|
||||
// WriteTo writes the object on an io.Writer.
|
||||
// To ensure optimal efficiency and minimal allocations, the user is encouraged
|
||||
// to provide a struct implementing the interface buffer.Writer, which defines
|
||||
// a subset of the method of the bufio.Writer.
|
||||
// If w is not compliant to the buffer.Writer interface, it will be wrapped in
|
||||
// a new bufio.Writer.
|
||||
// For additional information, see lattigo/utils/buffer/writer.go.
|
||||
// BinarySize returns the size in bytes of the object
|
||||
// when encoded using Encode.
|
||||
func (m Matrix[T]) BinarySize() (size int) {
|
||||
|
||||
if s, isSizable := any(new(T)).(BinarySizer); !isSizable {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), s))
|
||||
}
|
||||
|
||||
size += 8
|
||||
|
||||
for _, v := range m {
|
||||
size += (*Vector[T])(&v).BinarySize()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
|
||||
// interface, and will write exactly object.BinarySize() bytes on w.
|
||||
//
|
||||
// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go),
|
||||
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
|
||||
// is preferable to pass a buffer.Writer directly:
|
||||
//
|
||||
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
|
||||
// io.Writer in a pre-allocated bufio.Writer.
|
||||
// - When writing to a pre-allocated var b []byte, it is preferable to pass
|
||||
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
|
||||
func (m *Matrix[T]) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
if w, isWritable := any(new(T)).(io.WriterTo); !isWritable {
|
||||
@@ -71,13 +90,17 @@ func (m *Matrix[T]) WriteTo(w io.Writer) (n int64, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrom reads on the object from an io.Writer.
|
||||
// To ensure optimal efficiency and minimal allocations, the user is encouraged
|
||||
// to provide a struct implementing the interface buffer.Reader, which defines
|
||||
// a subset of the method of the bufio.Reader.
|
||||
// If r is not compliant to the buffer.Reader interface, it will be wrapped in
|
||||
// a new bufio.Reader.
|
||||
// For additional information, see lattigo/utils/buffer/reader.go.
|
||||
// ReadFrom reads on the object from an io.Writer. It implements the
|
||||
// io.ReaderFrom interface.
|
||||
//
|
||||
// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go),
|
||||
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
|
||||
// is preferable to pass a buffer.Reader directly:
|
||||
//
|
||||
// - When reading multiple values from a io.Reader, it is preferable to first
|
||||
// first wrap io.Reader in a pre-allocated bufio.Reader.
|
||||
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
|
||||
// as w (see lattigo/utils/buffer/buffer.go).
|
||||
func (m *Matrix[T]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
|
||||
if r, isReadable := any(new(T)).(io.ReaderFrom); !isReadable {
|
||||
@@ -113,19 +136,17 @@ func (m *Matrix[T]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// BinarySize returns the size in bytes of the object
|
||||
// when encoded using Encode.
|
||||
func (m Matrix[T]) BinarySize() (size int) {
|
||||
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
|
||||
func (m *Matrix[T]) MarshalBinary() (p []byte, err error) {
|
||||
buf := buffer.NewBufferSize(m.BinarySize())
|
||||
_, err = m.WriteTo(buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
if s, isSizable := any(new(T)).(BinarySizer); !isSizable {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), s))
|
||||
}
|
||||
|
||||
size += 8
|
||||
|
||||
for _, v := range m {
|
||||
size += (*Vector[T])(&v).BinarySize()
|
||||
}
|
||||
// UnmarshalBinary decodes a slice of bytes generated by
|
||||
// MarshalBinary or WriteTo on the object.
|
||||
func (m *Matrix[T]) UnmarshalBinary(p []byte) (err error) {
|
||||
_, err = m.ReadFrom(buffer.NewBuffer(p))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -177,17 +198,3 @@ func (m *Matrix[T]) Decode(p []byte) (n int, err error) {
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
|
||||
func (m *Matrix[T]) MarshalBinary() (p []byte, err error) {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
_, err = m.WriteTo(buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes a slice of bytes generated by
|
||||
// MarshalBinary or WriteTo on the object.
|
||||
func (m *Matrix[T]) UnmarshalBinary(p []byte) (err error) {
|
||||
_, err = m.ReadFrom(bytes.NewBuffer(p))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package structs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
@@ -12,21 +10,22 @@ import (
|
||||
"github.com/tuneinsight/lattigo/v4/utils/buffer"
|
||||
)
|
||||
|
||||
type binarySerializer interface {
|
||||
encoding.BinaryMarshaler
|
||||
encoding.BinaryUnmarshaler
|
||||
io.WriterTo
|
||||
io.ReaderFrom
|
||||
// Encoder
|
||||
// Decoder
|
||||
}
|
||||
// type binarySerializer interface {
|
||||
// encoding.BinaryMarshaler
|
||||
// encoding.BinaryUnmarshaler
|
||||
// io.WriterTo
|
||||
// io.ReaderFrom
|
||||
// // Encoder
|
||||
// // Decoder
|
||||
// }
|
||||
|
||||
type Vector[T any] []T
|
||||
|
||||
// CopyNew creates a copy of the oject.
|
||||
func (v Vector[T]) CopyNew() *Vector[T] {
|
||||
|
||||
if c, isCopiable := any(new(T)).(CopyNewer[T]); !isCopiable {
|
||||
var ct *T
|
||||
if c, isCopiable := any(ct).(CopyNewer[T]); !isCopiable {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), c))
|
||||
}
|
||||
|
||||
@@ -37,30 +36,50 @@ func (v Vector[T]) CopyNew() *Vector[T] {
|
||||
return &vcpy
|
||||
}
|
||||
|
||||
// WriteTo writes the object on an io.Writer.
|
||||
// To ensure optimal efficiency and minimal allocations, the user is encouraged
|
||||
// to provide a struct implementing the interface buffer.Writer, which defines
|
||||
// a subset of the method of the bufio.Writer.
|
||||
// If w is not compliant to the buffer.Writer interface, it will be wrapped in
|
||||
// a new bufio.Writer.
|
||||
// For additional information, see lattigo/utils/buffer/writer.go.
|
||||
// BinarySize returns the size in bytes of the object
|
||||
// when encoded using Encode.
|
||||
func (v Vector[T]) BinarySize() (size int) {
|
||||
|
||||
var st *T
|
||||
if s, isSizable := any(st).(BinarySizer); !isSizable {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", st, s))
|
||||
}
|
||||
|
||||
size += 8
|
||||
for _, c := range v {
|
||||
size += any(&c).(BinarySizer).BinarySize()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
|
||||
// interface, and will write exactly object.BinarySize() bytes on w.
|
||||
//
|
||||
// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go),
|
||||
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
|
||||
// is preferable to pass a buffer.Writer directly:
|
||||
//
|
||||
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
|
||||
// io.Writer in a pre-allocated bufio.Writer.
|
||||
// - When writing to a pre-allocated var b []byte, it is preferable to pass
|
||||
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
|
||||
func (v *Vector[T]) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
if w, isWritable := any(new(T)).(io.WriterTo); !isWritable {
|
||||
return 0, fmt.Errorf("vector component of type %T does not comply to %T", new(T), w)
|
||||
var o *T
|
||||
if wt, isWritable := any(o).(io.WriterTo); !isWritable {
|
||||
return 0, fmt.Errorf("vector component of type %T does not comply to %T", o, wt)
|
||||
}
|
||||
|
||||
switch w := w.(type) {
|
||||
case buffer.Writer:
|
||||
vval := *v
|
||||
|
||||
var inc int
|
||||
if inc, err = buffer.WriteInt(w, len(vval)); err != nil {
|
||||
if inc, err = buffer.WriteInt(w, len(*v)); err != nil {
|
||||
return int64(inc), err
|
||||
}
|
||||
n += int64(inc)
|
||||
|
||||
for _, c := range vval {
|
||||
for _, c := range *v {
|
||||
inc, err := any(&c).(io.WriterTo).WriteTo(w)
|
||||
n += inc
|
||||
if err != nil {
|
||||
@@ -75,20 +94,24 @@ func (v *Vector[T]) WriteTo(w io.Writer) (n int64, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrom reads on the object from an io.Writer.
|
||||
// To ensure optimal efficiency and minimal allocations, the user is encouraged
|
||||
// to provide a struct implementing the interface buffer.Reader, which defines
|
||||
// a subset of the method of the bufio.Reader.
|
||||
// If r is not compliant to the buffer.Reader interface, it will be wrapped in
|
||||
// a new bufio.Reader.
|
||||
// For additional information, see lattigo/utils/buffer/reader.go.
|
||||
// ReadFrom reads on the object from an io.Writer. It implements the
|
||||
// io.ReaderFrom interface.
|
||||
//
|
||||
// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go),
|
||||
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
|
||||
// is preferable to pass a buffer.Reader directly:
|
||||
//
|
||||
// - When reading multiple values from a io.Reader, it is preferable to first
|
||||
// first wrap io.Reader in a pre-allocated bufio.Reader.
|
||||
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
|
||||
// as w (see lattigo/utils/buffer/buffer.go).
|
||||
func (v *Vector[T]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
|
||||
if r, isReadable := any(new(T)).(io.ReaderFrom); !isReadable {
|
||||
return 0, fmt.Errorf("vector component of type %T does not comply to %T", new(T), r)
|
||||
var rt *T
|
||||
if r, isReadable := any(rt).(io.ReaderFrom); !isReadable {
|
||||
return 0, fmt.Errorf("vector component of type %T does not comply to %T", rt, r)
|
||||
}
|
||||
|
||||
// TODO: when has access to Reader's buffer, call Decode ?
|
||||
switch r := r.(type) {
|
||||
case buffer.Reader:
|
||||
var size int
|
||||
@@ -119,18 +142,17 @@ func (v *Vector[T]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// BinarySize returns the size in bytes of the object
|
||||
// when encoded using Encode.
|
||||
func (v Vector[T]) BinarySize() (size int) {
|
||||
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
|
||||
func (v *Vector[T]) MarshalBinary() (p []byte, err error) {
|
||||
buf := buffer.NewBufferSize(v.BinarySize())
|
||||
_, err = v.WriteTo(buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
if s, isSizable := any(new(T)).(BinarySizer); !isSizable {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), s))
|
||||
}
|
||||
|
||||
size += 8
|
||||
for _, c := range v {
|
||||
size += any(&c).(BinarySizer).BinarySize()
|
||||
}
|
||||
// UnmarshalBinary decodes a slice of bytes generated by
|
||||
// MarshalBinary or WriteTo on the object.
|
||||
func (v *Vector[T]) UnmarshalBinary(p []byte) (err error) {
|
||||
_, err = v.ReadFrom(buffer.NewBuffer(p))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -138,8 +160,9 @@ func (v Vector[T]) BinarySize() (size int) {
|
||||
// and returns the number of bytes written.
|
||||
func (v *Vector[T]) Encode(b []byte) (n int, err error) {
|
||||
|
||||
if e, isEncodable := any(new(T)).(Encoder); !isEncodable {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), e))
|
||||
var et *T
|
||||
if e, isEncodable := any(et).(Encoder); !isEncodable {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", et, e))
|
||||
}
|
||||
|
||||
vval := *v
|
||||
@@ -149,7 +172,7 @@ func (v *Vector[T]) Encode(b []byte) (n int, err error) {
|
||||
|
||||
var inc int
|
||||
for _, c := range vval {
|
||||
if inc, err := any(&c).(Encoder).Encode(b[n:]); err != nil {
|
||||
if inc, err = any(&c).(Encoder).Encode(b[n:]); err != nil {
|
||||
return n + inc, err
|
||||
}
|
||||
n += inc
|
||||
@@ -166,7 +189,7 @@ func (v *Vector[T]) Decode(p []byte) (n int, err error) {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), d))
|
||||
}
|
||||
|
||||
size := int(binary.LittleEndian.Uint64(p[n:])) // TODO: there is a bug here but it is not caught by the tests.
|
||||
size := int(binary.LittleEndian.Uint64(p))
|
||||
n += 8
|
||||
|
||||
if cap(*v) < size {
|
||||
@@ -185,16 +208,20 @@ func (v *Vector[T]) Decode(p []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
|
||||
func (v *Vector[T]) MarshalBinary() (p []byte, err error) {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
_, err = v.WriteTo(buf)
|
||||
return buf.Bytes(), err
|
||||
type Equatable[T any] interface {
|
||||
Equal(*T) bool
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes a slice of bytes generated by
|
||||
// MarshalBinary or WriteTo on the object.
|
||||
func (v *Vector[T]) UnmarshalBinary(p []byte) (err error) {
|
||||
_, err = v.ReadFrom(bytes.NewBuffer(p))
|
||||
return
|
||||
func (v Vector[T]) Equal(other Vector[T]) bool {
|
||||
|
||||
if d, isEquatable := any(new(T)).(Equatable[T]); !isEquatable {
|
||||
panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), d))
|
||||
}
|
||||
|
||||
isEqual := true
|
||||
for i, v := range v {
|
||||
isEqual = isEqual && any(&v).(Equatable[T]).Equal(&other[i])
|
||||
}
|
||||
|
||||
return isEqual
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user