Skip to content

Commit 740c41a

Browse files
Merge pull request #10 from heartical/feature/extended-containers
[Experimental ] feat: implement extended containers (ADR 001) to lift 13-bit size limit
2 parents bf9e015 + 7c7ec68 commit 740c41a

17 files changed

Lines changed: 2506 additions & 38 deletions

access/extended.go

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
package access
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
7+
"github.com/quickwritereader/PackOS/typetags"
8+
)
9+
10+
// Segment represents a segment in an extended container
11+
type Segment struct {
12+
Data []byte // Segment data
13+
SelfOffset uint32 // Offset of this segment's header within container
14+
Continuation uint32 // Offset of next segment header (or EndOfChain)
15+
IsExtended bool // Whether this segment is itself an extended container
16+
}
17+
18+
// Triplet tracks the relationship between nested containers
19+
type Triplet struct {
20+
ParentSegment []byte // Reference to parent segment buffer
21+
NextOffsetAddr int // Address within parent segment where nextOffset should be written
22+
ActualSegment []byte // The actual segment data
23+
IsExtended bool // Whether this segment is an extended container
24+
SelfOffset uint32 // Absolute offset for extended header
25+
Continuation uint32 // Continuation offset for extended header
26+
}
27+
28+
// ExtendedContainer manages extended containers with automatic segmentation
29+
type ExtendedContainer struct {
30+
segments []Segment // All segments in this container
31+
triplets []Triplet // Tracked triplets for nested containers
32+
current *PutAccess // Current segment being written
33+
pivotSize int // Size threshold for creating new segments
34+
isExtended bool // Whether this container is in extended mode
35+
parent *ExtendedContainer // Parent container (nil for root)
36+
parentOffsetAddr int // Where our header is in parent's offsets
37+
}
38+
39+
// NewExtendedContainer creates a new extended container
40+
func NewExtendedContainer(pivotSize int) *ExtendedContainer {
41+
if pivotSize <= 0 {
42+
pivotSize = 4096 // 4KB default
43+
}
44+
if pivotSize > 8192 {
45+
pivotSize = 8192 // Max 8KB for optimization
46+
}
47+
48+
return &ExtendedContainer{
49+
segments: make([]Segment, 0, 4),
50+
triplets: make([]Triplet, 0, 8),
51+
current: NewPutAccess(),
52+
pivotSize: pivotSize,
53+
isExtended: false,
54+
parent: nil,
55+
parentOffsetAddr: -1,
56+
}
57+
}
58+
59+
// newNestedContainer creates a nested extended container
60+
func newNestedContainer(parent *ExtendedContainer, offsetAddr int) *ExtendedContainer {
61+
return &ExtendedContainer{
62+
segments: make([]Segment, 0, 4),
63+
triplets: parent.triplets, // Share triplets with parent
64+
current: NewPutAccess(),
65+
pivotSize: parent.pivotSize,
66+
isExtended: false,
67+
parent: parent,
68+
parentOffsetAddr: offsetAddr,
69+
}
70+
}
71+
72+
// currentSize returns the current segment size including headers
73+
func (ec *ExtendedContainer) currentSize() int {
74+
if ec.current == nil {
75+
return 0
76+
}
77+
headerSize := len(ec.current.offsets) + 2 // +2 for TypeEnd
78+
return headerSize + ec.current.position
79+
}
80+
81+
// checkThreshold checks if adding data would exceed the pivot size
82+
func (ec *ExtendedContainer) checkThreshold(additional int) bool {
83+
return ec.currentSize()+additional > ec.pivotSize
84+
}
85+
86+
// finalizeSegment completes the current segment and starts a new one
87+
func (ec *ExtendedContainer) finalizeSegment() error {
88+
if ec.current.position == 0 && len(ec.current.offsets) == 0 {
89+
return nil // Empty segment
90+
}
91+
92+
// Complete current segment
93+
ec.current.offsets = binary.LittleEndian.AppendUint16(ec.current.offsets,
94+
typetags.EncodeEnd(ec.current.position))
95+
96+
// Pack the segment
97+
segmentData := ec.current.Pack()
98+
99+
// Create segment
100+
segment := Segment{
101+
Data: segmentData,
102+
IsExtended: false, // Will be updated if needed
103+
}
104+
105+
ec.segments = append(ec.segments, segment)
106+
107+
// Reset for next segment
108+
ec.current = NewPutAccess()
109+
110+
// Switch to extended mode after first segment
111+
if !ec.isExtended && len(ec.segments) == 1 {
112+
ec.isExtended = true
113+
}
114+
115+
return nil
116+
}
117+
118+
// Add adds data with automatic segmentation
119+
func (ec *ExtendedContainer) Add(adder func(*PutAccess), dataSize int) error {
120+
// Handle very large data
121+
if dataSize > ec.pivotSize {
122+
// Create nested extended container for large data
123+
nested := ec.BeginNested(typetags.TypeTuple)
124+
adder(nested.current)
125+
nested.isExtended = true // Force extended mode
126+
return ec.EndNested(nested)
127+
}
128+
129+
// Check if we need a new segment
130+
if ec.checkThreshold(dataSize) {
131+
if err := ec.finalizeSegment(); err != nil {
132+
return err
133+
}
134+
}
135+
136+
// Add data to current segment
137+
adder(ec.current)
138+
return nil
139+
}
140+
141+
// BeginNested starts a nested container
142+
func (ec *ExtendedContainer) BeginNested(tag typetags.Type) *ExtendedContainer {
143+
// Record where our header will be in parent's offsets
144+
offsetAddr := len(ec.current.offsets)
145+
146+
// Write placeholder header
147+
ec.current.offsets = binary.LittleEndian.AppendUint16(ec.current.offsets,
148+
typetags.EncodeHeader(ec.current.position, tag))
149+
150+
// Create nested container
151+
return newNestedContainer(ec, offsetAddr)
152+
}
153+
154+
// BeginTuple starts a tuple that may become extended
155+
func (ec *ExtendedContainer) BeginTuple() *ExtendedContainer {
156+
return ec.BeginNested(typetags.TypeTuple)
157+
}
158+
159+
// BeginMap starts a map that may become extended
160+
func (ec *ExtendedContainer) BeginMap() *ExtendedContainer {
161+
return ec.BeginNested(typetags.TypeMap)
162+
}
163+
164+
// EndNested ends a nested container
165+
func (ec *ExtendedContainer) EndNested(nested *ExtendedContainer) error {
166+
// Pack the nested container
167+
nestedData, err := nested.Pack()
168+
if err != nil {
169+
return err
170+
}
171+
172+
// Check if nested container needs to be extended
173+
needsExtension := len(nestedData) > ec.pivotSize || nested.isExtended
174+
175+
// Create triplet for tracking
176+
triplet := Triplet{
177+
ParentSegment: ec.current.buf,
178+
NextOffsetAddr: nested.parentOffsetAddr,
179+
ActualSegment: nestedData,
180+
IsExtended: needsExtension,
181+
}
182+
183+
// Add to triplets (shared with parent chain)
184+
ec.triplets = append(ec.triplets, triplet)
185+
186+
if needsExtension {
187+
// Update parent header to extended container type
188+
if triplet.NextOffsetAddr >= 0 && triplet.NextOffsetAddr+2 <= len(ec.current.offsets) {
189+
currentHeader := binary.LittleEndian.Uint16(ec.current.offsets[triplet.NextOffsetAddr:])
190+
offset, _ := typetags.DecodeHeader(currentHeader)
191+
newHeader := typetags.EncodeHeader(offset, typetags.TypeExtendedTagContainer)
192+
binary.LittleEndian.PutUint16(ec.current.offsets[triplet.NextOffsetAddr:], newHeader)
193+
}
194+
}
195+
196+
// Store the nested data
197+
ec.current.buf = append(ec.current.buf, nestedData...)
198+
ec.current.position = len(ec.current.buf)
199+
200+
return nil
201+
}
202+
203+
// buildExtendedContainer builds the final extended container structure
204+
func (ec *ExtendedContainer) buildExtendedContainer() ([]byte, error) {
205+
if len(ec.segments) == 0 {
206+
return nil, fmt.Errorf("no segments to build")
207+
}
208+
209+
// Build payload with extended headers
210+
var currentOffset uint32 = 0
211+
payload := make([]byte, 0)
212+
213+
for i, segment := range ec.segments {
214+
selfOffset := currentOffset
215+
var continuation uint32
216+
217+
if i < len(ec.segments)-1 {
218+
// Next header will be at currentOffset + ExtendedHeaderSize + segment length
219+
continuation = currentOffset + typetags.ExtendedHeaderSize + uint32(len(segment.Data))
220+
} else {
221+
continuation = typetags.EndOfChain
222+
}
223+
224+
// Add extended header
225+
payload = append(payload,
226+
typetags.EncodeExtendedHeader(selfOffset, continuation)...)
227+
228+
// Add segment data
229+
payload = append(payload, segment.Data...)
230+
231+
// Update segment metadata
232+
ec.segments[i].SelfOffset = selfOffset
233+
ec.segments[i].Continuation = continuation
234+
235+
currentOffset += typetags.ExtendedHeaderSize + uint32(len(segment.Data))
236+
}
237+
238+
// Create container headers
239+
headers := make([]byte, 0, 4)
240+
headers = binary.LittleEndian.AppendUint16(headers,
241+
typetags.EncodeHeader(4, typetags.TypeExtendedTagContainer))
242+
243+
// Handle large payloads (>8191 bytes)
244+
payloadSize := len(payload)
245+
max13Bit := 8191
246+
if payloadSize < max13Bit {
247+
max13Bit = payloadSize
248+
}
249+
headers = binary.LittleEndian.AppendUint16(headers,
250+
typetags.EncodeEnd(max13Bit))
251+
252+
// Combine headers and payload
253+
result := make([]byte, 0, len(headers)+payloadSize)
254+
result = append(result, headers...)
255+
result = append(result, payload...)
256+
257+
return result, nil
258+
}
259+
260+
// Pack finalizes and returns the packed container
261+
func (ec *ExtendedContainer) Pack() ([]byte, error) {
262+
// Finalize current segment if it has data
263+
if ec.current.position > 0 || len(ec.current.offsets) > 0 {
264+
if err := ec.finalizeSegment(); err != nil {
265+
return nil, err
266+
}
267+
}
268+
269+
// If no segments, return empty
270+
if len(ec.segments) == 0 {
271+
return []byte{}, nil
272+
}
273+
274+
// If single segment and not extended, return as-is
275+
if len(ec.segments) == 1 && !ec.isExtended {
276+
return ec.segments[0].Data, nil
277+
}
278+
279+
// Build extended container
280+
return ec.buildExtendedContainer()
281+
}
282+
283+
// GetTriplets returns all tracked triplets
284+
func (ec *ExtendedContainer) GetTriplets() []Triplet {
285+
return ec.triplets
286+
}
287+
288+
// SegmentCount returns the number of segments
289+
func (ec *ExtendedContainer) SegmentCount() int {
290+
return len(ec.segments)
291+
}
292+
293+
// IsExtended returns whether this container is in extended mode
294+
func (ec *ExtendedContainer) IsExtended() bool {
295+
return ec.isExtended
296+
}
297+
298+
// Convenience methods for common data types
299+
func (ec *ExtendedContainer) AddInt16(v int16) error {
300+
return ec.Add(func(pa *PutAccess) {
301+
pa.AddInt16(v)
302+
}, 2)
303+
}
304+
305+
func (ec *ExtendedContainer) AddInt32(v int32) error {
306+
return ec.Add(func(pa *PutAccess) {
307+
pa.AddInt32(v)
308+
}, 4)
309+
}
310+
311+
func (ec *ExtendedContainer) AddInt64(v int64) error {
312+
return ec.Add(func(pa *PutAccess) {
313+
pa.AddInt64(v)
314+
}, 8)
315+
}
316+
317+
func (ec *ExtendedContainer) AddString(s string) error {
318+
return ec.Add(func(pa *PutAccess) {
319+
pa.AddString(s)
320+
}, len(s))
321+
}
322+
323+
func (ec *ExtendedContainer) AddBytes(b []byte) error {
324+
return ec.Add(func(pa *PutAccess) {
325+
pa.AddBytes(b)
326+
}, len(b))
327+
}
328+
329+
func (ec *ExtendedContainer) AddBool(b bool) error {
330+
return ec.Add(func(pa *PutAccess) {
331+
pa.AddBool(b)
332+
}, 1)
333+
}
334+
335+
func (ec *ExtendedContainer) AddFloat32(v float32) error {
336+
return ec.Add(func(pa *PutAccess) {
337+
pa.AddFloat32(v)
338+
}, 4)
339+
}
340+
341+
func (ec *ExtendedContainer) AddFloat64(v float64) error {
342+
return ec.Add(func(pa *PutAccess) {
343+
pa.AddFloat64(v)
344+
}, 8)
345+
}

0 commit comments

Comments
 (0)