1 /**
2  * ae.sys.dataset
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.sys.dataset;
15 
16 import std.algorithm.mutation : move;
17 import std.range.primitives : ElementType, front;
18 
19 import ae.sys.data;
20 import ae.utils.array : asBytes, as;
21 import ae.utils.vec;
22 
23 /// Copy a `Data` array's contents to a specified buffer.
24 T[] copyTo(R, T)(auto ref R data, T[] buffer)
25 if (is(ElementType!R == TData!T))
26 {
27 	size_t pos = 0;
28 	foreach (ref d; data)
29 	{
30 		d.enter((scope contents) {
31 			buffer[pos .. pos + contents.length] = contents[];
32 			pos += contents.length;
33 		});
34 	}
35 	assert(pos == buffer.length);
36 	return buffer;
37 }
38 
39 deprecated void[] copyTo(R)(auto ref R data, void[] buffer)
40 if (is(ElementType!R == Data))
41 {
42 	return data.copyTo(cast(ubyte[])buffer);
43 }
44 
45 /// Join an array of Data to a single Data.
46 TData!(DataElementType!(ElementType!R)) joinData(R)(auto ref R data)
47 if (is(ElementType!R == TData!T, T))
48 {
49 	alias T = DataElementType!(ElementType!R);
50 
51 	if (data.length == 0)
52 		return TData!T();
53 	else
54 	if (data.length == 1)
55 		return data[0];
56 
57 	size_t size = 0;
58 	foreach (ref d; data)
59 		size += d.length;
60 	TData!T result = TData!T(size);
61 	result.enter((scope T[] contents) {
62 		data.copyTo(contents);
63 	});
64 	return result;
65 }
66 
67 unittest
68 {
69 	assert(([TData!int([1]), TData!int([2])].joinData().unsafeContents) == [1, 2]);
70 	assert(cast(int[])([Data([1].asBytes), Data([2].asBytes)].joinData().unsafeContents) == [1, 2]);
71 }
72 
73 /// Join an array of Data to a memory block on the managed heap.
74 DataElementType!(ElementType!R)[] joinToGC(R)(auto ref R data)
75 if (is(ElementType!R == TData!T, T))
76 {
77 	size_t size = 0;
78 	foreach (ref d; data)
79 		size += d.length;
80 	auto result = new DataElementType!(ElementType!R)[size];
81 	data.copyTo(result);
82 	return result;
83 }
84 
85 unittest
86 {
87 	assert(([TData!int([1]), TData!int([2])].joinToGC()) == [1, 2]);
88 	assert(cast(int[])([Data([1].asBytes), Data([2].asBytes)].joinToGC()) == [1, 2]);
89 }
90 
91 deprecated @property void[] joinToHeap(R)(auto ref R data)
92 if (is(ElementType!R == Data))
93 { return data.joinToGC(); }
94 
95 deprecated unittest
96 {
97 	assert(cast(int[])([Data([1].asBytes), Data([2].asBytes)].joinToHeap()) == [1, 2]);
98 }
99 
100 /// A vector of `Data` with deterministic lifetime.
101 alias DataVec = Vec!Data;
102 
103 /// Remove and return the specified number of bytes from the given `Data` array.
104 DataVec shift(ref DataVec data, size_t amount)
105 {
106 	auto bytes = data.bytes;
107 	auto result = bytes[0..amount];
108 	data = bytes[amount..bytes.length];
109 	return result;
110 }
111 
112 /// Return a type that's indexable to access individual bytes,
113 /// and sliceable to get an array of `Data` over the specified
114 /// byte range. No actual `Data` concatenation is done.
115 @property DataSetBytes bytes(Data[] data) { return DataSetBytes(data); }
116 @property DataSetBytes bytes(ref DataVec data) { return DataSetBytes(data[]); } /// ditto
117 
118 /// ditto
119 struct DataSetBytes
120 {
121 	Data[] data; /// Underlying `Data[]`.
122 
123 	ubyte opIndex(size_t offset)
124 	{
125 		size_t index = 0;
126 		while (index < data.length && data[index].length <= offset)
127 		{
128 			offset -= data[index].length;
129 			index++;
130 		}
131 		return data[index].asDataOf!ubyte[offset];
132 	} ///
133 
134 	DataVec opSlice()
135 	{
136 		return DataVec(data);
137 	} ///
138 
139 	DataVec opSlice(size_t start, size_t end)
140 	{
141 		auto range = DataVec(data);
142 		while (range.length && range[0].length <= start)
143 		{
144 			start -= range[0].length;
145 			end   -= range[0].length;
146 			range.popFront();
147 		}
148 		if (range.length==0)
149 		{
150 			assert(start==0, "Range error");
151 			return range;
152 		}
153 
154 		size_t endIndex = 0;
155 		while (endIndex < range.length && range[endIndex].length < end)
156 		{
157 			end -= range[endIndex].length;
158 			endIndex++;
159 		}
160 		range.length = endIndex + 1;
161 		range[$-1] = range[$-1][0..end];
162 		range[0  ] = range[0  ][start..range[0].length];
163 		return range;
164 	} ///
165 
166 	@property
167 	size_t length()
168 	{
169 		size_t result = 0;
170 		foreach (ref d; data)
171 			result += d.length;
172 		return result;
173 	} ///
174 
175 	size_t opDollar(size_t pos)()
176 	{
177 		static assert(pos == 0);
178 		return length;
179 	} ///
180 }
181 
182 unittest
183 {
184 	DataVec ds;
185 	string s;
186 
187 	ds = DataVec(
188 		Data("aaaaa".asBytes),
189 	);
190 	s = ds.joinToGC().as!string;
191 	assert(s == "aaaaa");
192 	s = ds.bytes[].joinToGC().as!string;
193 	assert(s == "aaaaa");
194 	s = ds.bytes[1..4].joinToGC().as!string;
195 	assert(s == "aaa");
196 
197 	ds = DataVec(
198 		Data("aaaaa".asBytes),
199 		Data("bbbbb".asBytes),
200 		Data("ccccc".asBytes),
201 	);
202 	auto dsb = ds.bytes;
203 	assert(dsb.length == 15);
204 	assert(dsb.length == 15);
205 	assert(dsb[ 4]=='a');
206 	assert(dsb[ 5]=='b');
207 	assert(dsb[ 9]=='b');
208 	assert(dsb[10]=='c');
209 	s = dsb[ 3..12].joinToGC().as!string;
210 	assert(s == "aabbbbbcc");
211 	s = ds.joinToGC().as!string;
212 	assert(s == "aaaaabbbbbccccc", s);
213 	s = dsb[ 0.. 6].joinToGC().as!string;
214 	assert(s == "aaaaab");
215 	s = dsb[ 9..15].joinToGC().as!string;
216 	assert(s == "bccccc");
217 	s = dsb[ 0.. 0].joinToGC().as!string;
218 	assert(s == "");
219 	s = dsb[15..15].joinToGC().as!string;
220 	assert(s == "");
221 }