internal/pipewire: size without serialisation
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m21s
Test / Hakurei (push) Successful in 3m16s
Test / Hpkg (push) Successful in 4m12s
Test / Sandbox (race detector) (push) Successful in 4m29s
Test / Hakurei (race detector) (push) Successful in 5m13s
Test / Flake checks (push) Successful in 1m28s

This is required to achieve zero allocation (other than reflect).

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-11-27 02:15:44 +09:00
parent 563b5e66fc
commit 73987be7d4
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
4 changed files with 90 additions and 8 deletions

View File

@ -46,6 +46,14 @@ type ClientInfo struct {
Props *SPADict
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *ClientInfo) Size() Word {
return SizePrefix +
Size(SizeInt) +
Size(SizeLong) +
c.Props.Size()
}
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *ClientInfo) MarshalBinary() ([]byte, error) { return Marshal(c) }
@ -58,6 +66,9 @@ type ClientUpdateProperties struct {
Props *SPADict
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *ClientUpdateProperties) Size() Word { return SizePrefix + c.Props.Size() }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *ClientUpdateProperties) MarshalBinary() ([]byte, error) { return Marshal(c) }

View File

@ -102,6 +102,19 @@ type CoreInfo struct {
Props *SPADict
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *CoreInfo) Size() Word {
return SizePrefix +
Size(SizeInt) +
Size(SizeInt) +
SizeString[Word](c.UserName) +
SizeString[Word](c.HostName) +
SizeString[Word](c.Version) +
SizeString[Word](c.Name) +
Size(SizeLong) +
c.Props.Size()
}
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *CoreInfo) MarshalBinary() ([]byte, error) { return Marshal(c) }
@ -116,6 +129,9 @@ type CoreDone struct {
Sequence Int
}
// Size satisfies [KnownSize] with a constant value.
func (c *CoreDone) Size() Word { return SizePrefix + Size(SizeInt) + Size(SizeInt) }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *CoreDone) MarshalBinary() ([]byte, error) { return Marshal(c) }
@ -133,6 +149,14 @@ type CoreBoundProps struct {
Props *SPADict
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (c *CoreBoundProps) Size() Word {
return SizePrefix +
Size(SizeInt) +
Size(SizeInt) +
c.Props.Size()
}
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal].
func (c *CoreBoundProps) MarshalBinary() ([]byte, error) { return Marshal(c) }
@ -145,6 +169,9 @@ type CoreHello struct {
Version Int
}
// Size satisfies [KnownSize] with a constant value.
func (c *CoreHello) Size() Word { return SizePrefix + Size(SizeInt) }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [MarshalAppend].
func (c *CoreHello) MarshalBinary() ([]byte, error) {
return MarshalAppend(make([]byte, 0, 24), c)
@ -168,6 +195,9 @@ type CoreSync struct {
Sequence Int
}
// Size satisfies [KnownSize] with a constant value.
func (c *CoreSync) Size() Word { return SizePrefix + Size(SizeInt) + Size(SizeInt) }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [MarshalAppend].
func (c *CoreSync) MarshalBinary() ([]byte, error) {
return MarshalAppend(make([]byte, 0, 40), c)
@ -191,6 +221,9 @@ type CoreGetRegistry struct {
NewID Int
}
// Size satisfies [KnownSize] with a constant value.
func (c *CoreGetRegistry) Size() Word { return SizePrefix + Size(SizeInt) + Size(SizeInt) }
// MarshalBinary satisfies [encoding.BinaryMarshaler] via [MarshalAppend].
func (c *CoreGetRegistry) MarshalBinary() ([]byte, error) {
return MarshalAppend(make([]byte, 0, 40), c)

View File

@ -44,11 +44,11 @@ const (
SizePrefix = SizeSPrefix + SizeTPrefix
// SizeId is the fixed, unpadded size of a [SPA_TYPE_Id] value.
SizeId = 4
SizeId Word = 4
// SizeInt is the fixed, unpadded size of a [SPA_TYPE_Int] value.
SizeInt = 4
SizeInt Word = 4
// SizeLong is the fixed, unpadded size of a [SPA_TYPE_Long] value.
SizeLong = 8
SizeLong Word = 8
)
/* Basic types */
@ -86,6 +86,24 @@ const (
_SPA_TYPE_LAST // not part of ABI
)
// A KnownSize value has known POD encoded size before serialisation.
type KnownSize interface {
// Size returns the POD encoded size of the receiver.
Size() Word
}
// PaddingSize returns the padding size corresponding to a wire size.
func PaddingSize[W Word | int](wireSize W) W { return (SizeAlign - (wireSize)%SizeAlign) % SizeAlign }
// PaddedSize returns the padded size corresponding to a wire size.
func PaddedSize[W Word | int](wireSize W) W { return wireSize + PaddingSize(wireSize) }
// Size returns prefixed and padded size corresponding to a wire size.
func Size[W Word | int](wireSize W) W { return SizePrefix + PaddedSize(wireSize) }
// SizeString returns prefixed and padded size corresponding to a string.
func SizeString[W Word | int](s string) W { return Size(W(len(s)) + 1) }
// PODMarshaler is the interface implemented by an object that can
// marshal itself into PipeWire POD encoding.
type PODMarshaler interface {
@ -125,14 +143,13 @@ func appendInner(data []byte, f func(data []byte) ([]byte, error)) ([]byte, erro
}
size := len(rData) - len(data) + SizeSPrefix
paddingSize := (SizeAlign - (size)%SizeAlign) % SizeAlign
// compensated for size and type prefix
wireSize := size - SizePrefix
if wireSize > math.MaxUint32 {
return data, UnsupportedSizeError(wireSize)
}
binary.NativeEndian.PutUint32(rData[len(data)-SizeSPrefix:len(data)], Word(wireSize))
rData = append(rData, make([]byte, paddingSize)...)
rData = append(rData, make([]byte, PaddingSize(size))...)
return rData, nil
}
@ -242,7 +259,7 @@ func UnmarshalNext(data []byte, v any) (size Word, err error) {
}
err = unmarshalValue(data, rv.Elem(), &size)
// prefix and padding size
size += SizePrefix + (SizeAlign-(size)%SizeAlign)%SizeAlign
size = Size(size)
return
}
@ -325,9 +342,8 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil {
return err
}
paddingSize := (SizeAlign - (fieldWireSize)%SizeAlign) % SizeAlign
// bounds check completed in successful call to unmarshalValue
data = data[SizePrefix+fieldWireSize+paddingSize:]
data = data[Size(fieldWireSize):]
}
if len(data) != 0 {
@ -441,6 +457,17 @@ type SPADict struct {
Items []SPADictItem
}
// Size satisfies [KnownSize] with a value computed at runtime.
func (d *SPADict) Size() Word {
// struct prefix, NItems value
size := SizePrefix + int(Size(SizeInt))
for i := range d.Items {
size += SizeString[int](d.Items[i].Key)
size += SizeString[int](d.Items[i].Value)
}
return Word(size)
}
// MarshalPOD satisfies [PODMarshaler] as [SPADict] violates the POD type system.
func (d *SPADict) MarshalPOD() ([]byte, error) {
return appendInner(nil, func(dataPrefix []byte) (data []byte, err error) {

View File

@ -5,11 +5,14 @@ import (
"encoding/json"
"reflect"
"testing"
"hakurei.app/internal/pipewire"
)
type encodingTestCases[V any, S interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
*V
}] []struct {
// Uninterpreted name of subtest.
@ -51,6 +54,14 @@ func (testCases encodingTestCases[V, S]) run(t *testing.T) {
t.Fatalf("MarshalBinary: %#v, want %#v", gotData, tc.wantData)
}
})
if s, ok := any(&tc.value).(pipewire.KnownSize); ok {
t.Run("size", func(t *testing.T) {
if got := int(s.Size()); got != len(tc.wantData) {
t.Errorf("Size: %d, want %d", got, len(tc.wantData))
}
})
}
})
}
}