diff --git a/util/infinitechan.go b/util/infinitechan.go new file mode 100644 index 00000000..5ce07a30 --- /dev/null +++ b/util/infinitechan.go @@ -0,0 +1,84 @@ +// Mgmt +// Copyright (C) James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this program, or any covered work, by linking or combining it +// with embedded mcl code and modules (and that the embedded mcl code and +// modules which link with this program, contain a copy of their source code in +// the authoritative form) containing parts covered by the terms of any other +// license, the licensors of this program grant you additional permission to +// convey the resulting work. Furthermore, the licensors of this program grant +// the original author, James Shubin, additional permission to update this +// additional permission if he deems it necessary to achieve the goals of this +// additional permission. + +package util + +// InfiniteChan is a buffered channel that has a theoretical infinite size. Of +// could you can run out of memory though. This is the same idea as a +// `make(chan T, $size)` but where $size grows to however big you need it to be. +type InfiniteChan[T any] struct { + In chan<- T + Out <-chan T +} + +// NewInfiniteChan builds a new InfiniteChat of the correct type. +func NewInfiniteChan[T any]() *InfiniteChan[T] { + in := make(chan T) + out := make(chan T) + + go func() { + defer close(out) + + buffer := make([]T, 0) // "infinite" + closed := false + + for { + if closed && len(buffer) == 0 { + // Clean shutdown: all data sent, now close and exit + return + } + + var next T // next value to send + var ch chan T // blocks unless "enabled" below + + // We have at least one value, so "enable" the send chan. + if len(buffer) > 0 { + next = buffer[0] + ch = out + } + + select { + case val, ok := <-in: // recv + if !ok { + closed = true + continue + } + buffer = append(buffer, val) + + case ch <- next: // send + buffer = buffer[1:] + } + } + }() + + return &InfiniteChan[T]{ + In: in, + Out: out, + } +} diff --git a/util/infinitechan_test.go b/util/infinitechan_test.go new file mode 100644 index 00000000..b1341aae --- /dev/null +++ b/util/infinitechan_test.go @@ -0,0 +1,69 @@ +// Mgmt +// Copyright (C) James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this program, or any covered work, by linking or combining it +// with embedded mcl code and modules (and that the embedded mcl code and +// modules which link with this program, contain a copy of their source code in +// the authoritative form) containing parts covered by the terms of any other +// license, the licensors of this program grant you additional permission to +// convey the resulting work. Furthermore, the licensors of this program grant +// the original author, James Shubin, additional permission to update this +// additional permission if he deems it necessary to achieve the goals of this +// additional permission. + +//go:build !root + +package util + +import ( + "testing" +) + +func TestInfiniteChan(t *testing.T) { + ch := NewInfiniteChan[string]() + + expected := []string{"one", "two", "three", "four", "five"} + + // producer + go func() { + for _, val := range expected { + ch.In <- val + } + close(ch.In) + }() + + // consumer + var result []string + for val := range ch.Out { + result = append(result, val) + } + + // check length + if len(result) != len(expected) { + t.Errorf("expected: %d items, actual: %d", len(expected), len(result)) + return + } + + // check ordering and values + for i := range expected { + if result[i] != expected[i] { + t.Errorf("expected(%d): %v, actual: %v", i, expected[i], result[i]) + } + } +}