summaryrefslogtreecommitdiff
path: root/src/util/SliceBuffer.hxx
blob: c61f164f49a24147ee0e8d4cf21097632f099c48 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
 * Copyright (C) 2003-2013 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 "gcc.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;
	};

	/**
	 * The maximum number of slices in this container.
	 */
	const unsigned n_max;

	/**
	 * 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;

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

	Slice *const data;

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

	size_t CalcAllocationSize() const {
		return n_max * sizeof(Slice);
	}

public:
	SliceBuffer(unsigned _count)
		:n_max(_count), n_initialized(0), n_allocated(0),
		 data((Slice *)HugeAllocate(CalcAllocationSize())),
		 available(nullptr) {
		assert(n_max > 0);
	}

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

		HugeFree(data, CalcAllocationSize());
	}

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

	/**
	 * @return true if buffer allocation (by the constructor) has failed
	 */
	bool IsOOM() {
		return data == nullptr;
	}

	unsigned GetCapacity() const {
		return n_max;
	}

	bool IsEmpty() const {
		return n_allocated == 0;
	}

	bool IsFull() const {
		return n_allocated == n_max;
	}

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

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

			available = &data[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) {
		assert(n_initialized <= n_max);
		assert(n_allocated > 0);
		assert(n_allocated <= n_initialized);

		Slice *slice = reinterpret_cast<Slice *>(value);
		assert(slice >= data && slice < data + n_max);

		/* 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) {
			HugeDiscard(data, CalcAllocationSize());
			n_initialized = 0;
			available = nullptr;
		}
	}
};

#endif