1 /** 2 This is helpful to break dependency upon dplug:core. 3 4 Copyright: Guillaume Piolats 2022. 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module audioformats.internals; 8 9 10 import core.stdc.stdlib: malloc, free, realloc; 11 import core.stdc.string: memcpy; 12 import core.exception: onOutOfMemoryErrorNoGC; 13 import std.conv: emplace; 14 import std.traits; 15 16 static immutable string 17 kErrorUnsupportedEncodingFormat = "Unsupported encoding format, maybe check your audio-formats configuration", 18 kErrorDecoderInitializationFailed = "Decoder initialization failed", 19 kErrorFileOpenFailed = "Couldn't open file", 20 kErrorFlushFailed = "Flushing stream failed", 21 kErrorDecodingError = "Decoder encountered an error", 22 kErrorEncodingError = "Encoder encountered an error", 23 kErrorUnknownFormat = "Cannot decode stream: unrecognized encoding."; 24 25 // 26 // Constructing and destroying without the GC. 27 // 28 29 /// Allocates and construct a struct or class object. 30 /// Returns: Newly allocated object. 31 auto mallocNew(T, Args...)(Args args) 32 { 33 static if (is(T == class)) 34 immutable size_t allocSize = __traits(classInstanceSize, T); 35 else 36 immutable size_t allocSize = T.sizeof; 37 38 void* rawMemory = malloc(allocSize); 39 if (!rawMemory) 40 onOutOfMemoryErrorNoGC(); 41 42 static if (is(T == class)) 43 { 44 T obj = emplace!T(rawMemory[0 .. allocSize], args); 45 } 46 else 47 { 48 T* obj = cast(T*)rawMemory; 49 emplace!T(obj, args); 50 } 51 52 return obj; 53 } 54 55 /// Destroys and frees a class object created with $(D mallocEmplace). 56 void destroyFree(T)(T p) if (is(T == class)) 57 { 58 if (p !is null) 59 { 60 destroyNoGC(p); 61 free(cast(void*)p); 62 } 63 } 64 65 /// Destroys and frees an interface object created with $(D mallocEmplace). 66 void destroyFree(T)(T p) if (is(T == interface)) 67 { 68 if (p !is null) 69 { 70 void* here = cast(void*)(cast(Object)p); 71 destroyNoGC(p); 72 free(cast(void*)here); 73 } 74 } 75 76 /// Destroys and frees a non-class object created with $(D mallocEmplace). 77 void destroyFree(T)(T* p) if (!is(T == class)) 78 { 79 if (p !is null) 80 { 81 destroyNoGC(p); 82 free(cast(void*)p); 83 } 84 } 85 86 87 unittest 88 { 89 class A 90 { 91 int _i; 92 this(int i) 93 { 94 _i = i; 95 } 96 } 97 98 struct B 99 { 100 int i; 101 } 102 103 void testMallocEmplace() 104 { 105 A a = mallocNew!A(4); 106 destroyFree(a); 107 108 B* b = mallocNew!B(5); 109 destroyFree(b); 110 } 111 112 testMallocEmplace(); 113 } 114 115 116 // 117 // Optimistic .destroy, which is @nogc nothrow by breaking the type-system 118 // 119 120 // for classes 121 void destroyNoGC(T)(T x) nothrow @nogc if (is(T == class) || is(T == interface)) 122 { 123 assumeNothrowNoGC( 124 (T x) 125 { 126 return destroy(x); 127 })(x); 128 } 129 130 // for struct 131 void destroyNoGC(T)(ref T obj) nothrow @nogc if (is(T == struct)) 132 { 133 assumeNothrowNoGC( 134 (ref T x) 135 { 136 return destroy(x); 137 })(obj); 138 } 139 140 void destroyNoGC(T)(ref T obj) nothrow @nogc 141 if (!is(T == struct) && !is(T == class) && !is(T == interface)) 142 { 143 assumeNothrowNoGC( 144 (ref T x) 145 { 146 return destroy(x); 147 })(obj); 148 } 149 150 151 auto assumeNothrowNoGC(T) (T t) 152 { 153 static if (isFunctionPointer!T || isDelegate!T) 154 { 155 enum attrs = functionAttributes!T | FunctionAttribute.nogc | FunctionAttribute.nothrow_; 156 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 157 } 158 else 159 static assert(false); 160 } 161 162 163 void reallocBuffer(T)(ref T[] buffer, size_t length) nothrow @nogc 164 { 165 static if (is(T == struct) && hasElaborateDestructor!T) 166 { 167 static assert(false); // struct with destructors not supported 168 } 169 170 /// Size 0 is special-case to free the slice. 171 if (length == 0) 172 { 173 free(buffer.ptr); 174 buffer = null; 175 return; 176 } 177 178 T* pointer = cast(T*) realloc(buffer.ptr, T.sizeof * length); 179 if (pointer is null) 180 buffer = null; // alignment 1 can still return null 181 else 182 buffer = pointer[0..length]; 183 } 184 185 186 alias CString = CStringImpl!char; 187 alias CString16 = CStringImpl!wchar; 188 189 /// Zero-terminated C string, to replace toStringz and toUTF16z 190 struct CStringImpl(CharType) if (is(CharType: char) || is(CharType: wchar)) 191 { 192 public: 193 nothrow: 194 @nogc: 195 196 const(CharType)* storage = null; 197 alias storage this; 198 199 200 this(const(CharType)[] s) 201 { 202 // Always copy. We can't assume anything about the input. 203 size_t len = s.length; 204 CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof); 205 buffer[0..len] = s[0..len]; 206 buffer[len] = '\0'; 207 storage = buffer; 208 wasAllocated = true; 209 } 210 211 // The constructor taking immutable can safely assume that such memory 212 // has been allocated by the GC or malloc, or an allocator that align 213 // pointer on at least 4 bytes. 214 this(immutable(CharType)[] s) 215 { 216 // Same optimizations that for toStringz 217 if (s.length == 0) 218 { 219 enum emptyString = cast(CharType[])""; 220 storage = emptyString.ptr; 221 return; 222 } 223 224 /* Peek past end of s[], if it's 0, no conversion necessary. 225 * Note that the compiler will put a 0 past the end of static 226 * strings, and the storage allocator will put a 0 past the end 227 * of newly allocated char[]'s. 228 */ 229 const(CharType)* p = s.ptr + s.length; 230 // Is p dereferenceable? A simple test: if the p points to an 231 // address multiple of 4, then conservatively assume the pointer 232 // might be pointing to another block of memory, which might be 233 // unreadable. Otherwise, it's definitely pointing to valid 234 // memory. 235 if ((cast(size_t) p & 3) && *p == 0) 236 { 237 storage = s.ptr; 238 return; 239 } 240 241 size_t len = s.length; 242 CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof); 243 buffer[0..len] = s[0..len]; 244 buffer[len] = '\0'; 245 storage = buffer; 246 wasAllocated = true; 247 } 248 249 ~this() 250 { 251 if (wasAllocated) 252 free(cast(void*)storage); 253 } 254 255 @disable this(this); 256 257 private: 258 bool wasAllocated = false; 259 } 260 261 262 /// Duplicates a slice with `malloc`. Equivalent to `.dup` 263 /// Has to be cleaned-up with `free(slice.ptr)` or `freeSlice(slice)`. 264 T[] mallocDup(T)(const(T)[] slice) nothrow @nogc if (!is(T == struct)) 265 { 266 T[] copy = mallocSliceNoInit!T(slice.length); 267 memcpy(copy.ptr, slice.ptr, slice.length * T.sizeof); 268 return copy; 269 } 270 271 /// Allocates a slice with `malloc`, but does not initialize the content. 272 T[] mallocSliceNoInit(T)(size_t count) nothrow @nogc 273 { 274 T* p = cast(T*) malloc(count * T.sizeof); 275 return p[0..count]; 276 } 277 278 279 /// Kind of a std::vector replacement. 280 /// Grow-only array, points to a (optionally aligned) memory location. 281 /// This can also work as an output range. 282 /// `Vec` is designed to work even when uninitialized, without `makeVec`. 283 struct Vec(T) 284 { 285 nothrow: 286 @nogc: 287 public 288 { 289 /// Creates an aligned buffer with given initial size. 290 this(size_t initialSize) 291 { 292 _size = 0; 293 _allocated = 0; 294 _data = null; 295 resize(initialSize); 296 } 297 298 ~this() 299 { 300 if (_data !is null) 301 { 302 free(_data); 303 _data = null; 304 _allocated = 0; 305 } 306 } 307 308 @disable this(this); 309 310 /// Returns: Length of buffer in elements. 311 size_t length() pure const 312 { 313 return _size; 314 } 315 316 /// Returns: Length of buffer in elements. 317 alias opDollar = length; 318 319 /// Resizes a buffer to hold $(D askedSize) elements. 320 void resize(size_t askedSize) 321 { 322 // grow only 323 if (_allocated < askedSize) 324 { 325 size_t numBytes = askedSize * 2 * T.sizeof; // gives 2x what is asked to make room for growth 326 _data = cast(T*)(realloc(_data, numBytes)); 327 _allocated = askedSize * 2; 328 } 329 _size = askedSize; 330 } 331 332 /// Pop last element 333 T popBack() 334 { 335 assert(_size > 0); 336 _size = _size - 1; 337 return _data[_size]; 338 } 339 340 /// Append an element to this buffer. 341 void pushBack(T x) 342 { 343 size_t i = _size; 344 resize(_size + 1); 345 _data[i] = x; 346 } 347 348 // DMD 2.088 deprecates the old D1-operators 349 static if (__VERSION__ >= 2088) 350 { 351 ///ditto 352 void opOpAssign(string op)(T x) if (op == "~") 353 { 354 pushBack(x); 355 } 356 } 357 else 358 { 359 ///ditto 360 void opCatAssign(T x) 361 { 362 pushBack(x); 363 } 364 } 365 366 // Output range support 367 alias put = pushBack; 368 369 /// Finds an item, returns -1 if not found 370 int indexOf(T x) 371 { 372 foreach(int i; 0..cast(int)_size) 373 if (_data[i] is x) 374 return i; 375 return -1; 376 } 377 378 /// Removes an item and replaces it by the last item. 379 /// Warning: this reorders the array. 380 void removeAndReplaceByLastElement(size_t index) 381 { 382 assert(index < _size); 383 _data[index] = _data[--_size]; 384 } 385 386 /// Removes an item and shift the rest of the array to front by 1. 387 /// Warning: O(N) complexity. 388 void removeAndShiftRestOfArray(size_t index) 389 { 390 assert(index < _size); 391 for (; index + 1 < _size; ++index) 392 _data[index] = _data[index+1]; 393 } 394 395 /// Appends another buffer to this buffer. 396 void pushBack(ref Vec other) 397 { 398 size_t oldSize = _size; 399 resize(_size + other._size); 400 memcpy(_data + oldSize, other._data, T.sizeof * other._size); 401 } 402 403 /// Appends a slice to this buffer. 404 /// `slice` should not belong to the same buffer _data. 405 void pushBack(T[] slice) 406 { 407 size_t oldSize = _size; 408 size_t newSize = _size + slice.length; 409 resize(newSize); 410 for (size_t n = 0; n < slice.length; ++n) 411 _data[oldSize + n] = slice[n]; 412 } 413 414 /// Returns: Raw pointer to data. 415 @property inout(T)* ptr() inout 416 { 417 return _data; 418 } 419 420 /// Returns: n-th element. 421 ref inout(T) opIndex(size_t i) pure inout 422 { 423 return _data[i]; 424 } 425 426 T opIndexAssign(T x, size_t i) 427 { 428 return _data[i] = x; 429 } 430 431 /// Sets size to zero, but keeps allocated buffers. 432 void clearContents() 433 { 434 _size = 0; 435 } 436 437 /// Returns: Whole content of the array in one slice. 438 inout(T)[] opSlice() inout 439 { 440 return opSlice(0, length()); 441 } 442 443 /// Returns: A slice of the array. 444 inout(T)[] opSlice(size_t i1, size_t i2) inout 445 { 446 return _data[i1 .. i2]; 447 } 448 449 /// Fills the buffer with the same value. 450 void fill(T x) 451 { 452 _data[0.._size] = x; 453 } 454 455 /// Move. Give up owner ship of the data. 456 T[] releaseData() 457 { 458 T[] data = _data[0.._size]; 459 this._data = null; 460 this._size = 0; 461 this._allocated = 0; 462 return data; 463 } 464 } 465 466 private 467 { 468 size_t _size = 0; 469 T* _data = null; 470 size_t _allocated = 0; 471 } 472 }