1 /// Translated from C to D 2 module soundio.os; 3 4 @nogc nothrow: 5 extern(C): __gshared: 6 7 8 public import soundio.api; 9 public import soundio.util; 10 11 import core.stdc.stdlib: free; 12 import core.stdc.string; 13 import core.stdc.errno; 14 15 // You may rely on the size of this struct as part of the API and ABI. 16 struct SoundIoOsMirroredMemory { 17 size_t capacity; 18 char* address; 19 void* priv; 20 } 21 22 version(Windows) { 23 public import core.sys.windows.windows; 24 public import core.sys.windows.mmsystem; 25 public import core.sys.windows.objbase; 26 27 alias INIT_ONCE = void*; 28 alias CONDITION_VARIABLE = void*; 29 enum INIT_ONCE_STATIC_INIT = null; 30 31 enum COINITBASE_MULTITHREADED = 0; 32 alias COINIT = int; // C enum 33 enum { 34 COINIT_APARTMENTTHREADED = 0x2, 35 COINIT_MULTITHREADED = COINITBASE_MULTITHREADED, 36 COINIT_DISABLE_OLE1DDE = 0x4, 37 COINIT_SPEED_OVER_MEMORY = 0x8, 38 } 39 40 extern(Windows) { 41 BOOL SleepConditionVariableCS(CONDITION_VARIABLE* ConditionVariable, PCRITICAL_SECTION CriticalSection, DWORD dwMilliseconds); 42 HRESULT CoInitializeEx(LPVOID, DWORD); 43 void CoUninitialize(); 44 void InitializeConditionVariable(CONDITION_VARIABLE* ConditionVariable); 45 void WakeConditionVariable(CONDITION_VARIABLE* ConditionVariable); 46 BOOL InitOnceBeginInitialize(INIT_ONCE* lpInitOnce, DWORD dwFlags, BOOL* fPending, VOID** lpContext); 47 BOOL InitOnceComplete(INIT_ONCE* lpInitOnce, DWORD dwFlags, VOID* lpContext); 48 enum INIT_ONCE_ASYNC = 0x00000002UL; 49 } 50 51 } else { 52 public import core.sys.posix.pthread; 53 public import core.sys.posix.unistd; 54 public import core.sys.posix.sys.mman; 55 enum MAP_ANONYMOUS = MAP_ANON; 56 57 // commented out of core.sys.posix.stdlib 58 extern(C) @nogc nothrow int mkstemp(char*); 59 } 60 61 version(FreeBSD) { 62 version = SOUNDIO_OS_KQUEUE; 63 } 64 version(OSX) { 65 version = SOUNDIO_OS_KQUEUE; 66 } 67 68 version(SOUNDIO_OS_KQUEUE) { 69 public import core.sys.posix.sys.types; 70 public import core.sys.posix.sys.time; 71 } 72 73 version(OSX) { 74 public import mach.clock; 75 public import mach.mach; 76 } 77 78 struct SoundIoOsThread { 79 version(Windows) { 80 HANDLE handle; 81 DWORD id; 82 } else { 83 pthread_attr_t attr; 84 bool attr_init; 85 86 pthread_t id; 87 bool running; 88 } 89 void* arg; 90 extern(C) @nogc nothrow void function(void* arg) run; 91 } 92 93 struct SoundIoOsMutex { 94 version(Windows) { 95 CRITICAL_SECTION id; 96 } else { 97 pthread_mutex_t id; 98 bool id_init; 99 } 100 } 101 102 version(SOUNDIO_OS_KQUEUE) { 103 static const(uintptr_t) notify_ident = 1; 104 struct SoundIoOsCond { 105 int kq_id; 106 } 107 } else version(Windows) { 108 struct SoundIoOsCond { 109 CONDITION_VARIABLE id; 110 CRITICAL_SECTION default_cs_id; 111 } 112 } else { 113 struct SoundIoOsCond { 114 pthread_cond_t id; 115 bool id_init; 116 117 pthread_condattr_t attr; 118 bool attr_init; 119 120 pthread_mutex_t default_mutex_id; 121 bool default_mutex_init; 122 } 123 } 124 125 version(Windows) { 126 static INIT_ONCE win32_init_once = INIT_ONCE_STATIC_INIT; 127 static double win32_time_resolution; 128 static SYSTEM_INFO win32_system_info; 129 } else { 130 static bool initialized = false; 131 static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; 132 version(OSX) { 133 static clock_serv_t cclock; 134 } 135 } 136 137 static int page_size; 138 139 double soundio_os_get_time() { 140 version(Windows) { 141 ulong time; 142 QueryPerformanceCounter(cast(LARGE_INTEGER*) &time); 143 return time * win32_time_resolution; 144 } else version(OSX) { 145 mach_timespec_t mts; 146 147 kern_return_t err = clock_get_time(cclock, &mts); 148 assert(!err); 149 150 double seconds = cast(double)mts.tv_sec; 151 seconds += (cast(double)mts.tv_nsec) / 1000000000.0; 152 153 return seconds; 154 } else { 155 timespec tms; 156 clock_gettime(CLOCK_MONOTONIC, &tms); 157 double seconds = cast(double)tms.tv_sec; 158 seconds += (cast(double)tms.tv_nsec) / 1000000000.0; 159 return seconds; 160 } 161 } 162 163 version(Windows) { 164 extern(Windows) static DWORD run_win32_thread(LPVOID userdata) { 165 SoundIoOsThread* thread = cast(SoundIoOsThread*)userdata; 166 HRESULT err = CoInitializeEx(null, COINIT_APARTMENTTHREADED); 167 assert(err == S_OK); 168 thread.run(thread.arg); 169 CoUninitialize(); 170 return 0; 171 } 172 } else { 173 static void assert_no_err(int err) { 174 assert(!err); 175 } 176 177 static void* run_pthread(void* userdata) { 178 SoundIoOsThread* thread = cast(SoundIoOsThread*)userdata; 179 thread.run(thread.arg); 180 return null; 181 } 182 } 183 184 private alias threadFunc = void function(void* arg); 185 private alias warningFunc = void function(); 186 int soundio_os_thread_create(threadFunc run, void* arg, warningFunc emit_rtprio_warning, SoundIoOsThread** out_thread) { 187 *out_thread = null; 188 189 SoundIoOsThread* thread = ALLOCATE!SoundIoOsThread(1); 190 if (!thread) { 191 soundio_os_thread_destroy(thread); 192 return SoundIoError.NoMem; 193 } 194 195 thread.run = run; 196 thread.arg = arg; 197 198 version(Windows) { 199 thread.handle = CreateThread(null, 0, &run_win32_thread, thread, 0, &thread.id); 200 if (!thread.handle) { 201 soundio_os_thread_destroy(thread); 202 return SoundIoError.SystemResources; 203 } 204 if (emit_rtprio_warning) { 205 if (!SetThreadPriority(thread.handle, THREAD_PRIORITY_TIME_CRITICAL)) { 206 emit_rtprio_warning(); 207 } 208 } 209 } else { 210 if (auto err = pthread_attr_init(&thread.attr)) { 211 soundio_os_thread_destroy(thread); 212 return SoundIoError.NoMem; 213 } 214 thread.attr_init = true; 215 216 if (emit_rtprio_warning) { 217 int max_priority = sched_get_priority_max(SCHED_FIFO); 218 if (max_priority == -1) { 219 soundio_os_thread_destroy(thread); 220 return SoundIoError.SystemResources; 221 } 222 223 if (auto err = pthread_attr_setschedpolicy(&thread.attr, SCHED_FIFO)) { 224 soundio_os_thread_destroy(thread); 225 return SoundIoError.SystemResources; 226 } 227 228 sched_param param; 229 param.sched_priority = max_priority; 230 if (auto err = pthread_attr_setschedparam(&thread.attr, ¶m)) { 231 soundio_os_thread_destroy(thread); 232 return SoundIoError.SystemResources; 233 } 234 235 } 236 237 if (auto err = pthread_create(&thread.id, &thread.attr, &run_pthread, thread)) { 238 if (err == EPERM && emit_rtprio_warning) { 239 emit_rtprio_warning(); 240 err = pthread_create(&thread.id, null, &run_pthread, thread); 241 } 242 if (err) { 243 soundio_os_thread_destroy(thread); 244 return SoundIoError.NoMem; 245 } 246 } 247 thread.running = true; 248 } 249 250 *out_thread = thread; 251 return 0; 252 } 253 254 void soundio_os_thread_destroy(SoundIoOsThread* thread) { 255 if (!thread) 256 return; 257 258 version(Windows) { 259 if (thread.handle) { 260 DWORD err = WaitForSingleObject(thread.handle, INFINITE); 261 assert(err != WAIT_FAILED); 262 BOOL ok = CloseHandle(thread.handle); 263 assert(ok); 264 } 265 } else { 266 267 if (thread.running) { 268 assert_no_err(pthread_join(thread.id, null)); 269 } 270 271 if (thread.attr_init) { 272 assert_no_err(pthread_attr_destroy(&thread.attr)); 273 } 274 } 275 276 free(thread); 277 } 278 279 SoundIoOsMutex* soundio_os_mutex_create() { 280 SoundIoOsMutex* mutex = ALLOCATE!SoundIoOsMutex( 1); 281 if (!mutex) { 282 soundio_os_mutex_destroy(mutex); 283 return null; 284 } 285 286 version(Windows) { 287 InitializeCriticalSection(&mutex.id); 288 } else { 289 if (auto err = pthread_mutex_init(&mutex.id, null)) { 290 soundio_os_mutex_destroy(mutex); 291 return null; 292 } 293 mutex.id_init = true; 294 } 295 296 return mutex; 297 } 298 299 void soundio_os_mutex_destroy(SoundIoOsMutex* mutex) { 300 if (!mutex) 301 return; 302 303 version(Windows) { 304 DeleteCriticalSection(&mutex.id); 305 } else { 306 if (mutex.id_init) { 307 assert_no_err(pthread_mutex_destroy(&mutex.id)); 308 } 309 } 310 311 free(mutex); 312 } 313 314 void soundio_os_mutex_lock(SoundIoOsMutex* mutex) { 315 version(Windows) { 316 EnterCriticalSection(&mutex.id); 317 } else { 318 assert_no_err(pthread_mutex_lock(&mutex.id)); 319 } 320 } 321 322 void soundio_os_mutex_unlock(SoundIoOsMutex* mutex) { 323 version(Windows) { 324 LeaveCriticalSection(&mutex.id); 325 } else { 326 assert_no_err(pthread_mutex_unlock(&mutex.id)); 327 } 328 } 329 330 SoundIoOsCond* soundio_os_cond_create() { 331 SoundIoOsCond* cond = ALLOCATE!SoundIoOsCond(1); 332 333 if (!cond) { 334 soundio_os_cond_destroy(cond); 335 return null; 336 } 337 338 version(Windows) { 339 InitializeConditionVariable(&cond.id); 340 InitializeCriticalSection(&cond.default_cs_id); 341 } else version(SOUNDIO_OS_KQUEUE) { 342 cond.kq_id = kqueue(); 343 if (cond.kq_id == -1) 344 return null; 345 } else { 346 if (pthread_condattr_init(&cond.attr)) { 347 soundio_os_cond_destroy(cond); 348 return null; 349 } 350 cond.attr_init = true; 351 352 if (pthread_condattr_setclock(&cond.attr, CLOCK_MONOTONIC)) { 353 soundio_os_cond_destroy(cond); 354 return null; 355 } 356 357 if (pthread_cond_init(&cond.id, &cond.attr)) { 358 soundio_os_cond_destroy(cond); 359 return null; 360 } 361 cond.id_init = true; 362 363 if ((pthread_mutex_init(&cond.default_mutex_id, null))) { 364 soundio_os_cond_destroy(cond); 365 return null; 366 } 367 cond.default_mutex_init = true; 368 } 369 370 return cond; 371 } 372 373 void soundio_os_cond_destroy(SoundIoOsCond* cond) { 374 if (!cond) 375 return; 376 377 version(Windows) { 378 DeleteCriticalSection(&cond.default_cs_id); 379 } else version(SOUNDIO_OS_KQUEUE) { 380 close(cond.kq_id); 381 } else { 382 if (cond.id_init) { 383 assert_no_err(pthread_cond_destroy(&cond.id)); 384 } 385 386 if (cond.attr_init) { 387 assert_no_err(pthread_condattr_destroy(&cond.attr)); 388 } 389 if (cond.default_mutex_init) { 390 assert_no_err(pthread_mutex_destroy(&cond.default_mutex_id)); 391 } 392 } 393 394 free(cond); 395 } 396 397 void soundio_os_cond_signal(SoundIoOsCond* cond, SoundIoOsMutex* locked_mutex) { 398 version(Windows) { 399 if (locked_mutex) { 400 WakeConditionVariable(&cond.id); 401 } else { 402 EnterCriticalSection(&cond.default_cs_id); 403 WakeConditionVariable(&cond.id); 404 LeaveCriticalSection(&cond.default_cs_id); 405 } 406 } else version(SOUNDIO_OS_KQUEUE) { 407 kevent kev; 408 timespec timeout = [ 0, 0 ]; 409 410 memset(&kev, 0, kev.sizeof); 411 kev.ident = notify_ident; 412 kev.filter = EVFILT_USER; 413 kev.fflags = NOTE_TRIGGER; 414 415 if (kevent(cond.kq_id, &kev, 1, null, 0, &timeout) == -1) { 416 if (errno == EINTR) 417 return; 418 if (errno == ENOENT) 419 return; 420 assert(0); // kevent signal error 421 } 422 } else { 423 if (locked_mutex) { 424 assert_no_err(pthread_cond_signal(&cond.id)); 425 } else { 426 assert_no_err(pthread_mutex_lock(&cond.default_mutex_id)); 427 assert_no_err(pthread_cond_signal(&cond.id)); 428 assert_no_err(pthread_mutex_unlock(&cond.default_mutex_id)); 429 } 430 } 431 } 432 433 void soundio_os_cond_timed_wait(SoundIoOsCond* cond, SoundIoOsMutex* locked_mutex, double seconds) { 434 version(Windows) { 435 CRITICAL_SECTION* target_cs; 436 if (locked_mutex) { 437 target_cs = &locked_mutex.id; 438 } else { 439 target_cs = &cond.default_cs_id; 440 EnterCriticalSection(&cond.default_cs_id); 441 } 442 DWORD ms = cast(int) (seconds * 1000.0); 443 SleepConditionVariableCS(&cond.id, target_cs, ms); 444 if (!locked_mutex) 445 LeaveCriticalSection(&cond.default_cs_id); 446 } else version(SOUNDIO_OS_KQUEUE) { 447 kevent kev; 448 kevent out_kev; 449 450 if (locked_mutex) 451 assert_no_err(pthread_mutex_unlock(&locked_mutex.id)); 452 453 memset(&kev, 0, kev.sizeof); 454 kev.ident = notify_ident; 455 kev.filter = EVFILT_USER; 456 kev.flags = EV_ADD | EV_CLEAR; 457 458 // this time is relative 459 timespec timeout; 460 timeout.tv_nsec = (seconds * 1000000000L); 461 timeout.tv_sec = timeout.tv_nsec / 1000000000L; 462 timeout.tv_nsec = timeout.tv_nsec % 1000000000L; 463 464 if (kevent(cond.kq_id, &kev, 1, &out_kev, 1, &timeout) == -1) { 465 if (errno == EINTR) 466 return; 467 assert(0); // kevent wait error 468 } 469 if (locked_mutex) 470 assert_no_err(pthread_mutex_lock(&locked_mutex.id)); 471 } else { 472 pthread_mutex_t* target_mutex; 473 if (locked_mutex) { 474 target_mutex = &locked_mutex.id; 475 } else { 476 target_mutex = &cond.default_mutex_id; 477 assert_no_err(pthread_mutex_lock(target_mutex)); 478 } 479 // this time is absolute 480 timespec tms; 481 clock_gettime(CLOCK_MONOTONIC, &tms); 482 tms.tv_nsec += cast(long) (seconds * 1000000000L); 483 tms.tv_sec += tms.tv_nsec / 1000000000L; 484 tms.tv_nsec = tms.tv_nsec % 1000000000L; 485 if (auto err = pthread_cond_timedwait(&cond.id, target_mutex, &tms)) { 486 assert(err != EPERM); 487 assert(err != EINVAL); 488 } 489 if (!locked_mutex) 490 assert_no_err(pthread_mutex_unlock(target_mutex)); 491 } 492 } 493 494 void soundio_os_cond_wait(SoundIoOsCond* cond, SoundIoOsMutex* locked_mutex) { 495 version(Windows) { 496 CRITICAL_SECTION* target_cs; 497 if (locked_mutex) { 498 target_cs = &locked_mutex.id; 499 } else { 500 target_cs = &cond.default_cs_id; 501 EnterCriticalSection(&cond.default_cs_id); 502 } 503 SleepConditionVariableCS(&cond.id, target_cs, INFINITE); 504 if (!locked_mutex) 505 LeaveCriticalSection(&cond.default_cs_id); 506 } else version(SOUNDIO_OS_KQUEUE) { 507 kevent kev; 508 kevent out_kev; 509 510 if (locked_mutex) 511 assert_no_err(pthread_mutex_unlock(&locked_mutex.id)); 512 513 memset(&kev, 0, kev.sizeof); 514 kev.ident = notify_ident; 515 kev.filter = EVFILT_USER; 516 kev.flags = EV_ADD | EV_CLEAR; 517 518 if (kevent(cond.kq_id, &kev, 1, &out_kev, 1, null) == -1) { 519 if (errno == EINTR) 520 return; 521 assert(0); // kevent wait error 522 } 523 if (locked_mutex) 524 assert_no_err(pthread_mutex_lock(&locked_mutex.id)); 525 } else { 526 pthread_mutex_t* target_mutex; 527 if (locked_mutex) { 528 target_mutex = &locked_mutex.id; 529 } else { 530 target_mutex = &cond.default_mutex_id; 531 assert_no_err(pthread_mutex_lock(&cond.default_mutex_id)); 532 } 533 if (auto err = pthread_cond_wait(&cond.id, target_mutex)) { 534 assert(err != EPERM); 535 assert(err != EINVAL); 536 } 537 if (!locked_mutex) 538 assert_no_err(pthread_mutex_unlock(&cond.default_mutex_id)); 539 } 540 } 541 542 static int internal_init() { 543 version(Windows) { 544 ulong frequency; 545 if (QueryPerformanceFrequency(cast(LARGE_INTEGER*) &frequency)) { 546 win32_time_resolution = 1.0 / cast(double) frequency; 547 } else { 548 return SoundIoError.SystemResources; 549 } 550 GetSystemInfo(&win32_system_info); 551 page_size = win32_system_info.dwAllocationGranularity; 552 } else { 553 page_size = cast(int) sysconf(_SC_PAGESIZE); 554 version(OSX) { 555 host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); 556 } 557 } 558 return 0; 559 } 560 561 int soundio_os_init() { 562 version(Windows) { 563 PVOID lpContext; 564 BOOL pending; 565 566 if (!InitOnceBeginInitialize(&win32_init_once, INIT_ONCE_ASYNC, &pending, &lpContext)) 567 return SoundIoError.SystemResources; 568 569 if (!pending) 570 return 0; 571 572 if (auto err = internal_init()) 573 return err; 574 575 if (!InitOnceComplete(&win32_init_once, INIT_ONCE_ASYNC, null)) 576 return SoundIoError.SystemResources; 577 } else { 578 assert_no_err(pthread_mutex_lock(&init_mutex)); 579 if (initialized) { 580 assert_no_err(pthread_mutex_unlock(&init_mutex)); 581 return 0; 582 } 583 initialized = true; 584 if (auto err = internal_init()) 585 return err; 586 assert_no_err(pthread_mutex_unlock(&init_mutex)); 587 } 588 589 return 0; 590 } 591 592 int soundio_os_page_size() { 593 return page_size; 594 } 595 596 pragma(inline, true) static size_t ceil_dbl_to_size_t(double x) { 597 const(double) truncation = cast(size_t)x; 598 return cast(size_t) (truncation + (truncation < x)); 599 } 600 601 int soundio_os_init_mirrored_memory(SoundIoOsMirroredMemory* mem, size_t requested_capacity) { 602 size_t actual_capacity = ceil_dbl_to_size_t(requested_capacity / cast(double)page_size) * page_size; 603 604 version(Windows) { 605 BOOL ok; 606 HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, null, PAGE_READWRITE, 0, cast(int) (actual_capacity * 2), null); 607 if (!hMapFile) 608 return SoundIoError.NoMem; 609 610 for (;;) { 611 // find a free address space with the correct size 612 char* address = cast(char*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, actual_capacity * 2); 613 if (!address) { 614 ok = CloseHandle(hMapFile); 615 assert(ok); 616 return SoundIoError.NoMem; 617 } 618 619 // found a big enough address space. hopefully it will remain free 620 // while we map to it. if not, we'll try again. 621 ok = UnmapViewOfFile(address); 622 assert(ok); 623 624 char* addr1 = cast(char*)MapViewOfFileEx(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, actual_capacity, address); 625 if (addr1 != address) { 626 DWORD err = GetLastError(); 627 if (err == ERROR_INVALID_ADDRESS) { 628 continue; 629 } else { 630 ok = CloseHandle(hMapFile); 631 assert(ok); 632 return SoundIoError.NoMem; 633 } 634 } 635 636 char* addr2 = cast(char*)MapViewOfFileEx(hMapFile, FILE_MAP_WRITE, 0, 0, 637 actual_capacity, address + actual_capacity); 638 if (addr2 != address + actual_capacity) { 639 ok = UnmapViewOfFile(addr1); 640 assert(ok); 641 642 DWORD err = GetLastError(); 643 if (err == ERROR_INVALID_ADDRESS) { 644 continue; 645 } else { 646 ok = CloseHandle(hMapFile); 647 assert(ok); 648 return SoundIoError.NoMem; 649 } 650 } 651 652 mem.priv = hMapFile; 653 mem.address = address; 654 break; 655 } 656 } else { 657 char[32] shm_path = "/dev/shm/soundio-XXXXXX\0"; 658 char[32] tmp_path = "/tmp/soundio-XXXXXX\0"; 659 char* chosen_path; 660 661 int fd = mkstemp(shm_path.ptr); 662 if (fd < 0) { 663 fd = mkstemp(tmp_path.ptr); 664 if (fd < 0) { 665 return SoundIoError.SystemResources; 666 } else { 667 chosen_path = tmp_path.ptr; 668 } 669 } else { 670 chosen_path = shm_path.ptr; 671 } 672 673 if (unlink(chosen_path)) { 674 close(fd); 675 return SoundIoError.SystemResources; 676 } 677 678 if (ftruncate(fd, actual_capacity)) { 679 close(fd); 680 return SoundIoError.SystemResources; 681 } 682 683 char* address = cast(char*)mmap(null, actual_capacity * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 684 if (address == MAP_FAILED) { 685 close(fd); 686 return SoundIoError.NoMem; 687 } 688 689 char* other_address = cast(char*)mmap(address, actual_capacity, PROT_READ|PROT_WRITE, 690 MAP_FIXED|MAP_SHARED, fd, 0); 691 if (other_address != address) { 692 munmap(address, 2 * actual_capacity); 693 close(fd); 694 return SoundIoError.NoMem; 695 } 696 697 other_address = cast(char*)mmap(address + actual_capacity, actual_capacity, 698 PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0); 699 if (other_address != address + actual_capacity) { 700 munmap(address, 2 * actual_capacity); 701 close(fd); 702 return SoundIoError.NoMem; 703 } 704 705 mem.address = address; 706 707 if (close(fd)) 708 return SoundIoError.SystemResources; 709 } 710 711 mem.capacity = actual_capacity; 712 return 0; 713 } 714 715 void soundio_os_deinit_mirrored_memory(SoundIoOsMirroredMemory* mem) { 716 if (!mem.address) 717 return; 718 version(Windows) { 719 BOOL ok; 720 ok = UnmapViewOfFile(mem.address); 721 assert(ok); 722 ok = UnmapViewOfFile(mem.address + mem.capacity); 723 assert(ok); 724 ok = CloseHandle(cast(HANDLE)mem.priv); 725 assert(ok); 726 } else { 727 int err = munmap(mem.address, 2 * mem.capacity); 728 assert(!err); 729 } 730 mem.address = null; 731 }