summaryrefslogtreecommitdiff
path: root/src/util/SliceBuffer.hxx
blob: 55572c592d64f291b017d3aae732ce46b377ff40 (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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
 * Copyright 2003-2017 The Music Player Daemon Project
 * http://www.musicpd.org
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifndef MPD_SLICE_BUFFER_HXX
#define MPD_SLICE_BUFFER_HXX

#include "HugeAllocator.hxx"
#include "Compiler.h"

#include <utility>
#include <new>

#include <assert.h>
#include <stddef.h>

/**
 * This class pre-allocates a certain number of objects, and allows
 * callers to allocate and free these objects ("slices").
 */
template<typename T>
class SliceBuffer {
	union Slice {
		Slice *next;

		T value;
	};

	HugeArray<Slice> buffer;

	/**
	 * The number of slices that are initialized.  This is used to
	 * avoid page faulting on the new allocation, so the kernel
	 * does not need to reserve physical memory pages.
	 */
	unsigned n_initialized = 0;

	/**
	 * The number of slices currently allocated.
	 */
	unsigned n_allocated = 0;

	/**
	 * Pointer to the first free element in the chain.
	 */
	Slice *available = nullptr;

public:
	SliceBuffer(unsigned _count)
		:buffer(_count) {
		buffer.ForkCow(false);
	}

	~SliceBuffer() noexcept {
		/* all slices must be freed explicitly, and this
		   assertion checks for leaks */
		assert(n_allocated == 0);
	}

	SliceBuffer(const SliceBuffer &other) = delete;
	SliceBuffer &operator=(const SliceBuffer &other) = delete;

	unsigned GetCapacity() const noexcept {
		return buffer.size();
	}

	bool empty() const noexcept {
		return n_allocated == 0;
	}

	bool IsFull() const noexcept {
		return n_allocated == buffer.size();
	}

	template<typename... Args>
	T *Allocate(Args&&... args) {
		assert(n_initialized <= buffer.size());
		assert(n_allocated <= n_initialized);

		if (available == nullptr) {
			if (n_initialized == buffer.size()) {
				/* out of (internal) memory, buffer is full */
				assert(n_allocated == buffer.size());
				return nullptr;
			}

			available = &buffer[n_initialized++];
			available->next = nullptr;
		}

		/* allocate a slice */
		T *value = &available->value;
		available = available->next;
		++n_allocated;

		/* construct the object */
		return ::new((void *)value) T(std::forward<Args>(args)...);
	}

	void Free(T *value) noexcept {
		assert(n_initialized <= buffer.size());
		assert(n_allocated > 0);
		assert(n_allocated <= n_initialized);

		Slice *slice = reinterpret_cast<Slice *>(value);
		assert(slice >= &buffer.front() && slice <= &buffer.back());

		/* destruct the object */
		value->~T();

		/* insert the slice in the "available" linked list */
		slice->next = available;
		available = slice;
		--n_allocated;

		/* give memory back to the kernel when the last slice
		   was freed */
		if (n_allocated == 0) {
			buffer.Discard();
			n_initialized = 0;
			available = nullptr;
		}
	}
};

#endif