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