package stario import ( "container/list" "errors" "sync" ) var ErrStarRingInvalidCapacity = errors.New("star ring capacity must be greater than zero") // StarRing keeps the newest bytes in memory with fixed capacity. // It never blocks writers: when capacity is exceeded, oldest bytes are dropped. type StarRing struct { mu sync.RWMutex cap int size int chunks list.List // each element: []byte } func NewStarRing(capacity int) (*StarRing, error) { if capacity <= 0 { return nil, ErrStarRingInvalidCapacity } return &StarRing{cap: capacity}, nil } func (s *StarRing) Capacity() int { s.mu.RLock() defer s.mu.RUnlock() return s.cap } func (s *StarRing) Len() int { s.mu.RLock() defer s.mu.RUnlock() return s.size } func (s *StarRing) Reset() { s.mu.Lock() defer s.mu.Unlock() s.size = 0 s.chunks.Init() } func (s *StarRing) Write(p []byte) (int, error) { if len(p) == 0 { return 0, nil } payload := append([]byte(nil), p...) s.mu.Lock() defer s.mu.Unlock() s.writeLocked(payload) return len(p), nil } func (s *StarRing) WriteString(text string) (int, error) { if len(text) == 0 { return 0, nil } return s.Write([]byte(text)) } func (s *StarRing) Snapshot() []byte { s.mu.RLock() defer s.mu.RUnlock() out := make([]byte, s.size) pos := 0 for node := s.chunks.Front(); node != nil; node = node.Next() { chunk, ok := node.Value.([]byte) if !ok || len(chunk) == 0 { continue } pos += copy(out[pos:], chunk) } return out } func (s *StarRing) writeLocked(payload []byte) { if len(payload) >= s.cap { payload = payload[len(payload)-s.cap:] s.chunks.Init() s.chunks.PushBack(payload) s.size = len(payload) return } s.chunks.PushBack(payload) s.size += len(payload) s.trimLocked() } func (s *StarRing) trimLocked() { if s.size <= s.cap { return } overflow := s.size - s.cap for overflow > 0 { front := s.chunks.Front() if front == nil { s.size = 0 return } head, ok := front.Value.([]byte) if !ok || len(head) == 0 { s.chunks.Remove(front) continue } if len(head) <= overflow { s.chunks.Remove(front) s.size -= len(head) overflow -= len(head) continue } trimmed := append([]byte(nil), head[overflow:]...) front.Value = trimmed s.size -= overflow overflow = 0 } }