summaryrefslogtreecommitdiff
path: root/lib/containers/linkedlist.go
blob: 07b4760b0450977dd01df9a87231b91c9b93846b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// Copyright (C) 2023  Luke Shumaker <lukeshu@lukeshu.com>
//
// SPDX-License-Identifier: GPL-2.0-or-later

package containers

import (
	"git.lukeshu.com/go/typedsync"
)

// LinkedListEntry [T] is an entry in a LinkedList [T].
type LinkedListEntry[T any] struct {
	older, newer *LinkedListEntry[T]
	Value        T
}

// LinkedList is a doubly-linked list.
//
// Rather than "head/tail", "front/back", or "next/prev", it has
// "oldest" and "newest".  This is for to make code using it clearer;
// as the motivation for the LinkedList is as an implementation detail
// in LRU caches and FIFO queues, where this temporal naming is
// meaningful.  Similarly, it does not implement many common features
// of a linked-list, because these applications do not need such
// features.
//
// An advantage over `container/list.List` is that LinkedList
// maintains a Pool of entries, so churning through the list does not
// churn out garbage.  However, LinkedList also has the disadvantages
// that it has fewer safety checks and fewer features in general.
type LinkedList[T any] struct {
	oldest, newest *LinkedListEntry[T]
	pool           typedsync.Pool[*LinkedListEntry[T]]
}

// IsEmpty returns whether the list empty or not.
func (l *LinkedList[T]) IsEmpty() bool {
	return l.oldest == nil
}

// Delete removes an entry from the list.  The entry is invalid once
// Delete returns, and should not be reused or have its .Value
// accessed.
//
// It is invalid (runtime-panic) to call Delete on a nil entry.
//
// It is invalid (corrupt the list) to call Delete on an entry that
// isn't in the list.
func (l *LinkedList[T]) Delete(entry *LinkedListEntry[T]) {
	if entry.newer == nil {
		l.newest = entry.older
	} else {
		entry.newer.older = entry.older
	}
	if entry.older == nil {
		l.oldest = entry.newer
	} else {
		entry.older.newer = entry.newer
	}

	*entry = LinkedListEntry[T]{} // no memory leaks
	l.pool.Put(entry)
}

// Store appends a value to the "newest" end of the list, returning
// the created entry.
func (l *LinkedList[T]) Store(val T) *LinkedListEntry[T] {
	entry, ok := l.pool.Get()
	if !ok {
		entry = new(LinkedListEntry[T])
	}
	*entry = LinkedListEntry[T]{
		older: l.newest,
		Value: val,
	}
	l.newest = entry
	if entry.older == nil {
		l.oldest = entry
	} else {
		entry.older.newer = entry
	}
	return entry
}

// MoveToNewest moves an entry fron any position in the list to the
// "newest" end of the list.  If the entry is already in the "newest"
// position, then MoveToNewest is a no-op.
//
// It is invalid (runtime-panic) to call MoveToNewest on a nil entry.
//
// It is invalid (corrupt the list) to call MoveToNewest on an entry
// that isn't in the list.
func (l *LinkedList[T]) MoveToNewest(entry *LinkedListEntry[T]) {
	if entry.newer == nil {
		// Already newest.
		return
	}
	entry.newer.older = entry.older
	if entry.older == nil {
		l.oldest = entry.newer
	} else {
		entry.older.newer = entry.newer
	}

	entry.older = l.newest
	l.newest.newer = entry

	entry.newer = nil
	l.newest = entry
}

// Oldest returns the entry at the "oldest" end of the list, or nil if
// the list is empty.
func (l *LinkedList[T]) Oldest() *LinkedListEntry[T] {
	return l.oldest
}