aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pipewire
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-11-27 02:15:44 +0900
committerOphestra <cat@gensokyo.uk>2025-11-27 02:17:38 +0900
commit73987be7d4c850f8b95babb8f68e6ccb6cafcee4 (patch)
treec46e77b552fe508b7970be199699b07ae86cb288 /internal/pipewire
parent563b5e66fc0dfe0abc4a4b8c96f0f89ef71c3422 (diff)
internal/pipewire: size without serialisation
This is required to achieve zero allocation (other than reflect). Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pipewire')
-rw-r--r--internal/pipewire/client.go11
-rw-r--r--internal/pipewire/core.go33
-rw-r--r--internal/pipewire/pod.go43
-rw-r--r--internal/pipewire/pod_test.go11
4 files changed, 90 insertions, 8 deletions
diff --git a/internal/pipewire/client.go b/internal/pipewire/client.go
index 0dce269b..0847ac0a 100644
--- a/internal/pipewire/client.go
+++ b/internal/pipewire/client.go
@@ -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) }
diff --git a/internal/pipewire/core.go b/internal/pipewire/core.go
index 407ea954..1ef2f790 100644
--- a/internal/pipewire/core.go
+++ b/internal/pipewire/core.go
@@ -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)
diff --git a/internal/pipewire/pod.go b/internal/pipewire/pod.go
index 97ba4d07..80c798e8 100644
--- a/internal/pipewire/pod.go
+++ b/internal/pipewire/pod.go
@@ -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) {
diff --git a/internal/pipewire/pod_test.go b/internal/pipewire/pod_test.go
index a39f9614..28e789f5 100644
--- a/internal/pipewire/pod_test.go
+++ b/internal/pipewire/pod_test.go
@@ -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))
+ }
+ })
+ }
})
}
}