Switching audio system to platform-agnostic primitives.

This commit is contained in:
Ben Vanik 2015-07-14 23:13:56 -07:00
parent 345fe60da0
commit a6012b73f4
13 changed files with 150 additions and 165 deletions

View File

@ -73,19 +73,14 @@ AudioSystem::AudioSystem(Emulator* emulator)
}
for (size_t i = 0; i < kMaximumClientCount; ++i) {
client_semaphores_[i] =
CreateSemaphore(NULL, 0, kMaximumQueuedFrames, NULL);
wait_handles_[i] = client_semaphores_[i];
xe::threading::Semaphore::Create(0, kMaximumQueuedFrames);
wait_handles_[i] = client_semaphores_[i].get();
}
shutdown_event_ = CreateEvent(NULL, TRUE, FALSE, NULL);
wait_handles_[kMaximumClientCount] = shutdown_event_;
shutdown_event_ = xe::threading::Event::CreateManualResetEvent(false);
wait_handles_[kMaximumClientCount] = shutdown_event_.get();
}
AudioSystem::~AudioSystem() {
for (size_t i = 0; i < kMaximumClientCount; ++i) {
CloseHandle(client_semaphores_[i]);
}
CloseHandle(shutdown_event_);
}
AudioSystem::~AudioSystem() = default;
X_STATUS AudioSystem::Setup() {
processor_ = emulator_->processor();
@ -111,18 +106,17 @@ void AudioSystem::WorkerThreadMain() {
// Main run loop.
while (worker_running_) {
auto result =
WaitForMultipleObjectsEx(DWORD(xe::countof(wait_handles_)),
wait_handles_, FALSE, INFINITE, FALSE);
if (result == WAIT_FAILED ||
result == WAIT_OBJECT_0 + kMaximumClientCount) {
auto result = xe::threading::WaitAny(
wait_handles_, DWORD(xe::countof(wait_handles_)), true);
if (result.first == xe::threading::WaitResult::kFailed ||
(result.first == xe::threading::WaitResult::kSuccess &&
result.second == kMaximumClientCount)) {
continue;
}
size_t pumped = 0;
if (result >= WAIT_OBJECT_0 &&
result <= WAIT_OBJECT_0 + (kMaximumClientCount - 1)) {
size_t index = result - WAIT_OBJECT_0;
if (result.first == xe::threading::WaitResult::kSuccess) {
size_t index = result.second;
do {
lock_.lock();
uint32_t client_callback = clients_[index].callback;
@ -138,8 +132,9 @@ void AudioSystem::WorkerThreadMain() {
pumped++;
index++;
} while (index < kMaximumClientCount &&
WaitForSingleObject(client_semaphores_[index], 0) ==
WAIT_OBJECT_0);
xe::threading::Wait(client_semaphores_[index].get(), false,
std::chrono::milliseconds(0)) ==
xe::threading::WaitResult::kSuccess);
}
if (!worker_running_) {
@ -160,7 +155,7 @@ void AudioSystem::Initialize() {}
void AudioSystem::Shutdown() {
worker_running_ = false;
SetEvent(shutdown_event_);
shutdown_event_->Set();
worker_thread_->Wait(0, 0, 0, nullptr);
worker_thread_.reset();
}
@ -172,9 +167,9 @@ X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg,
auto index = unused_clients_.front();
auto client_semaphore = client_semaphores_[index];
BOOL ret = ReleaseSemaphore(client_semaphore, kMaximumQueuedFrames, NULL);
assert_true(ret == TRUE);
auto client_semaphore = client_semaphores_[index].get();
auto ret = client_semaphore->Release(kMaximumQueuedFrames, nullptr);
assert_true(ret);
AudioDriver* driver;
auto result = CreateDriver(index, client_semaphore, &driver);
@ -215,13 +210,14 @@ void AudioSystem::UnregisterClient(size_t index) {
clients_[index] = {0};
unused_clients_.push(index);
// drain the semaphore of its count
auto client_semaphore = client_semaphores_[index];
DWORD wait_result;
// Drain the semaphore of its count.
auto client_semaphore = client_semaphores_[index].get();
xe::threading::WaitResult wait_result;
do {
wait_result = WaitForSingleObject(client_semaphore, 0);
} while (wait_result == WAIT_OBJECT_0);
assert_true(wait_result == WAIT_TIMEOUT);
wait_result = xe::threading::Wait(client_semaphore, false,
std::chrono::milliseconds(0));
} while (wait_result == xe::threading::WaitResult::kSuccess);
assert_true(wait_result == xe::threading::WaitResult::kTimeout);
}
} // namespace apu

View File

@ -14,11 +14,10 @@
#include <mutex>
#include <queue>
#include "xenia/base/threading.h"
#include "xenia/emulator.h"
#include "xenia/xbox.h"
typedef void* HANDLE;
namespace xe {
namespace kernel {
class XHostThread;
@ -48,7 +47,15 @@ class AudioSystem {
void UnregisterClient(size_t index);
void SubmitFrame(size_t index, uint32_t samples_ptr);
virtual X_STATUS CreateDriver(size_t index, HANDLE semaphore,
protected:
AudioSystem(Emulator* emulator);
virtual void Initialize();
void WorkerThreadMain();
virtual X_STATUS CreateDriver(size_t index,
xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) = 0;
virtual void DestroyDriver(AudioDriver* driver) = 0;
@ -56,15 +63,6 @@ class AudioSystem {
// XAUDIO2_MAX_QUEUED_BUFFERS))
static const size_t kMaximumQueuedFrames = 64;
protected:
virtual void Initialize();
private:
void WorkerThreadMain();
protected:
AudioSystem(Emulator* emulator);
Emulator* emulator_;
Memory* memory_;
cpu::Processor* processor_;
@ -83,9 +81,11 @@ class AudioSystem {
uint32_t wrapped_callback_arg;
} clients_[kMaximumClientCount];
HANDLE client_semaphores_[kMaximumClientCount];
HANDLE shutdown_event_; // Event is always there in case we have no clients.
HANDLE wait_handles_[kMaximumClientCount + 1];
std::unique_ptr<xe::threading::Semaphore>
client_semaphores_[kMaximumClientCount];
// Event is always there in case we have no clients.
std::unique_ptr<xe::threading::Event> shutdown_event_;
xe::threading::WaitHandle* wait_handles_[kMaximumClientCount + 1];
std::queue<size_t> unused_clients_;
};

View File

@ -23,7 +23,8 @@ NopAudioSystem::NopAudioSystem(Emulator* emulator) : AudioSystem(emulator) {}
NopAudioSystem::~NopAudioSystem() = default;
X_STATUS NopAudioSystem::CreateDriver(size_t index, HANDLE wait_handle,
X_STATUS NopAudioSystem::CreateDriver(size_t index,
xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) {
return X_STATUS_NOT_IMPLEMENTED;
}

View File

@ -23,7 +23,7 @@ class NopAudioSystem : public AudioSystem {
static std::unique_ptr<AudioSystem> Create(Emulator* emulator);
X_STATUS CreateDriver(size_t index, HANDLE wait_handle,
X_STATUS CreateDriver(size_t index, xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) override;
void DestroyDriver(AudioDriver* driver) override;
};

View File

@ -24,32 +24,27 @@ namespace xaudio2 {
class XAudio2AudioDriver::VoiceCallback : public IXAudio2VoiceCallback {
public:
VoiceCallback(HANDLE semaphore) : semaphore_(semaphore) {}
VoiceCallback(xe::threading::Semaphore* semaphore) : semaphore_(semaphore) {}
~VoiceCallback() {}
void OnStreamEnd() {}
void OnVoiceProcessingPassEnd() {}
void OnVoiceProcessingPassStart(uint32_t samples_required) {}
void OnBufferEnd(void* context) {
BOOL ret = ReleaseSemaphore(semaphore_, 1, NULL);
assert_true(ret == TRUE);
auto ret = semaphore_->Release(1, nullptr);
assert_true(ret);
}
void OnBufferStart(void* context) {}
void OnLoopEnd(void* context) {}
void OnVoiceError(void* context, HRESULT result) {}
private:
HANDLE semaphore_;
xe::threading::Semaphore* semaphore_ = nullptr;
};
XAudio2AudioDriver::XAudio2AudioDriver(Emulator* emulator, HANDLE semaphore)
: AudioDriver(emulator),
audio_(nullptr),
mastering_voice_(nullptr),
pcm_voice_(nullptr),
semaphore_(semaphore),
voice_callback_(nullptr),
current_frame_(0) {
XAudio2AudioDriver::XAudio2AudioDriver(Emulator* emulator,
xe::threading::Semaphore* semaphore)
: AudioDriver(emulator), semaphore_(semaphore) {
static_assert(frame_count_ == XAUDIO2_MAX_QUEUED_BUFFERS,
"xaudio header differs");
}

View File

@ -22,7 +22,7 @@ namespace xaudio2 {
class XAudio2AudioDriver : public AudioDriver {
public:
XAudio2AudioDriver(Emulator* emulator, HANDLE semaphore);
XAudio2AudioDriver(Emulator* emulator, xe::threading::Semaphore* semaphore);
~XAudio2AudioDriver() override;
void Initialize();
@ -30,13 +30,13 @@ class XAudio2AudioDriver : public AudioDriver {
void Shutdown();
private:
IXAudio2* audio_;
IXAudio2MasteringVoice* mastering_voice_;
IXAudio2SourceVoice* pcm_voice_;
HANDLE semaphore_;
IXAudio2* audio_ = nullptr;
IXAudio2MasteringVoice* mastering_voice_ = nullptr;
IXAudio2SourceVoice* pcm_voice_ = nullptr;
xe::threading::Semaphore* semaphore_ = nullptr;
class VoiceCallback;
VoiceCallback* voice_callback_;
VoiceCallback* voice_callback_ = nullptr;
static const uint32_t frame_count_ = 64;
static const uint32_t frame_channels_ = 6;
@ -44,7 +44,7 @@ class XAudio2AudioDriver : public AudioDriver {
static const uint32_t frame_samples_ = frame_channels_ * channel_samples_;
static const uint32_t frame_size_ = sizeof(float) * frame_samples_;
float frames_[frame_count_][frame_samples_];
uint32_t current_frame_;
uint32_t current_frame_ = 0;
};
} // namespace xaudio2

View File

@ -30,7 +30,8 @@ XAudio2AudioSystem::~XAudio2AudioSystem() {}
void XAudio2AudioSystem::Initialize() { AudioSystem::Initialize(); }
X_STATUS XAudio2AudioSystem::CreateDriver(size_t index, HANDLE semaphore,
X_STATUS XAudio2AudioSystem::CreateDriver(size_t index,
xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) {
assert_not_null(out_driver);
auto driver = new XAudio2AudioDriver(emulator_, semaphore);

View File

@ -23,7 +23,7 @@ class XAudio2AudioSystem : public AudioSystem {
static std::unique_ptr<AudioSystem> Create(Emulator* emulator);
X_RESULT CreateDriver(size_t index, HANDLE semaphore,
X_RESULT CreateDriver(size_t index, xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) override;
void DestroyDriver(AudioDriver* driver) override;

View File

@ -111,83 +111,73 @@ class WaitHandle {
public:
virtual ~WaitHandle() = default;
// Waits until the wait handle is in the signaled state, an alert triggers and
// a user callback is queued to the thread, or the timeout interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
inline WaitResult Wait(
bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitHandle::Wait(this, is_alertable, timeout);
}
// Waits until the wait handle is in the signaled state, an alert triggers and
// a user callback is queued to the thread, or the timeout interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
static WaitResult Wait(
WaitHandle* wait_handle, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
// Signals one object and waits on another object as a single operation.
// Waits until the wait handle is in the signaled state, an alert triggers and
// a user callback is queued to the thread, or the timeout interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
static WaitResult SignalAndWait(
WaitHandle* wait_handle_to_signal, WaitHandle* wait_handle_to_wait_on,
bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
// Waits until all of the specified objects are in the signaled state, a
// user callback is queued to the thread, or the time-out interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
inline static WaitResult WaitAll(
WaitHandle* wait_handles[], size_t wait_handle_count, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitMultiple(wait_handles, wait_handle_count, true, is_alertable,
timeout).first;
}
inline static WaitResult WaitAll(
std::vector<WaitHandle*> wait_handles, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitAll(wait_handles.data(), wait_handles.size(), is_alertable,
timeout);
}
// Waits until any of the specified objects are in the signaled state, a
// user callback is queued to the thread, or the time-out interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
// The second argument of the return tuple indicates which wait handle caused
// the wait to be satisfied or abandoned.
inline static std::pair<WaitResult, size_t> WaitAny(
WaitHandle* wait_handles[], size_t wait_handle_count, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitMultiple(wait_handles, wait_handle_count, false, is_alertable,
timeout);
}
inline static std::pair<WaitResult, size_t> WaitAny(
std::vector<WaitHandle*> wait_handles, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitAny(wait_handles.data(), wait_handles.size(), is_alertable,
timeout);
}
protected:
WaitHandle() = default;
// Returns the native handle of the object on the host system.
// This value is platform specific.
virtual void* native_handle() const = 0;
static std::pair<WaitResult, size_t> WaitMultiple(
WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all,
bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
protected:
WaitHandle() = default;
};
// Waits until the wait handle is in the signaled state, an alert triggers and
// a user callback is queued to the thread, or the timeout interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
WaitResult Wait(
WaitHandle* wait_handle, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
// Signals one object and waits on another object as a single operation.
// Waits until the wait handle is in the signaled state, an alert triggers and
// a user callback is queued to the thread, or the timeout interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
WaitResult SignalAndWait(
WaitHandle* wait_handle_to_signal, WaitHandle* wait_handle_to_wait_on,
bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
std::pair<WaitResult, size_t> WaitMultiple(
WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all,
bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
// Waits until all of the specified objects are in the signaled state, a
// user callback is queued to the thread, or the time-out interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
inline WaitResult WaitAll(
WaitHandle* wait_handles[], size_t wait_handle_count, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitMultiple(wait_handles, wait_handle_count, true, is_alertable,
timeout).first;
}
inline WaitResult WaitAll(
std::vector<WaitHandle*> wait_handles, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitAll(wait_handles.data(), wait_handles.size(), is_alertable,
timeout);
}
// Waits until any of the specified objects are in the signaled state, a
// user callback is queued to the thread, or the time-out interval elapses.
// If timeout is zero the call will return immediately instead of waiting and
// if the timeout is max() the wait will not time out.
// The second argument of the return tuple indicates which wait handle caused
// the wait to be satisfied or abandoned.
inline std::pair<WaitResult, size_t> WaitAny(
WaitHandle* wait_handles[], size_t wait_handle_count, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitMultiple(wait_handles, wait_handle_count, false, is_alertable,
timeout);
}
inline std::pair<WaitResult, size_t> WaitAny(
std::vector<WaitHandle*> wait_handles, bool is_alertable,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
return WaitAny(wait_handles.data(), wait_handles.size(), is_alertable,
timeout);
}
// Models a Win32-like event object.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx
class Event : public WaitHandle {

View File

@ -102,8 +102,8 @@ class Win32Handle : public T {
HANDLE handle_ = nullptr;
};
WaitResult WaitHandle::Wait(WaitHandle* wait_handle, bool is_alertable,
std::chrono::milliseconds timeout) {
WaitResult Wait(WaitHandle* wait_handle, bool is_alertable,
std::chrono::milliseconds timeout) {
HANDLE handle = wait_handle->native_handle();
DWORD result = WaitForSingleObjectEx(handle, DWORD(timeout.count()),
is_alertable ? TRUE : FALSE);
@ -122,10 +122,9 @@ WaitResult WaitHandle::Wait(WaitHandle* wait_handle, bool is_alertable,
}
}
WaitResult WaitHandle::SignalAndWait(WaitHandle* wait_handle_to_signal,
WaitHandle* wait_handle_to_wait_on,
bool is_alertable,
std::chrono::milliseconds timeout) {
WaitResult SignalAndWait(WaitHandle* wait_handle_to_signal,
WaitHandle* wait_handle_to_wait_on, bool is_alertable,
std::chrono::milliseconds timeout) {
HANDLE handle_to_signal = wait_handle_to_signal->native_handle();
HANDLE handle_to_wait_on = wait_handle_to_wait_on->native_handle();
DWORD result =
@ -146,9 +145,10 @@ WaitResult WaitHandle::SignalAndWait(WaitHandle* wait_handle_to_signal,
}
}
std::pair<WaitResult, size_t> WaitHandle::WaitMultiple(
WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all,
bool is_alertable, std::chrono::milliseconds timeout) {
std::pair<WaitResult, size_t> WaitMultiple(WaitHandle* wait_handles[],
size_t wait_handle_count,
bool wait_all, bool is_alertable,
std::chrono::milliseconds timeout) {
std::vector<HANDLE> handles(wait_handle_count);
for (size_t i = 0; i < wait_handle_count; ++i) {
handles[i] = wait_handles[i]->native_handle();

View File

@ -17,9 +17,9 @@ namespace xe {
namespace kernel {
typedef struct {
xe::be<DWORD> count;
xe::be<DWORD> state[5];
xe::be<BYTE> buffer[64];
xe::be<uint32_t> count;
xe::be<uint32_t> state[5];
uint8_t buffer[64];
} XECRYPT_SHA_STATE;
void XeCryptShaInit(pointer_t<XECRYPT_SHA_STATE> sha_state) {

View File

@ -7,6 +7,7 @@
******************************************************************************
*/
#include "xenia/base/debugging.h"
#include "xenia/base/logging.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/objects/xthread.h"
@ -17,7 +18,7 @@
namespace xe {
namespace kernel {
void DbgBreakPoint() { DebugBreak(); }
void DbgBreakPoint() { xe::debugging::Break(); }
DECLARE_XBOXKRNL_EXPORT(DbgBreakPoint, ExportTag::kImportant);
// https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
@ -79,13 +80,13 @@ void RtlRaiseException(pointer_t<X_EXCEPTION_RECORD> record) {
if (record->exception_code == 0xE06D7363) {
// C++ exception.
// http://blogs.msdn.com/b/oldnewthing/archive/2010/07/30/10044061.aspx
DebugBreak();
xe::debugging::Break();
return;
}
// TODO(benvanik): unwinding.
// This is going to suck.
DebugBreak();
xe::debugging::Break();
}
DECLARE_XBOXKRNL_EXPORT(RtlRaiseException, ExportTag::kImportant);
@ -94,7 +95,7 @@ void KeBugCheckEx(dword_t code, dword_t param1, dword_t param2, dword_t param3,
XELOGD("*** STOP: 0x%.8X (0x%.8X, 0x%.8X, 0x%.8X, 0x%.8X)", code, param1,
param2, param3, param4);
fflush(stdout);
DebugBreak();
xe::debugging::Break();
assert_always();
}
DECLARE_XBOXKRNL_EXPORT(KeBugCheckEx, ExportTag::kImportant);

View File

@ -128,7 +128,8 @@ X_STATUS XObject::Wait(uint32_t wait_reason, uint32_t processor_mode,
TimeoutTicksToMs(*opt_timeout)))
: std::chrono::milliseconds::max();
auto result = wait_handle->Wait(alertable ? true : false, timeout_ms);
auto result =
xe::threading::Wait(wait_handle, alertable ? true : false, timeout_ms);
switch (result) {
case xe::threading::WaitResult::kSuccess:
return X_STATUS_SUCCESS;
@ -136,7 +137,7 @@ X_STATUS XObject::Wait(uint32_t wait_reason, uint32_t processor_mode,
// Or X_STATUS_ALERTED?
return X_STATUS_USER_APC;
case xe::threading::WaitResult::kTimeout:
YieldProcessor();
xe::threading::MaybeYield();
return X_STATUS_TIMEOUT;
default:
case xe::threading::WaitResult::kAbandoned:
@ -153,7 +154,7 @@ X_STATUS XObject::SignalAndWait(XObject* signal_object, XObject* wait_object,
TimeoutTicksToMs(*opt_timeout)))
: std::chrono::milliseconds::max();
auto result = xe::threading::WaitHandle::SignalAndWait(
auto result = xe::threading::SignalAndWait(
signal_object->GetWaitHandle(), wait_object->GetWaitHandle(),
alertable ? true : false, timeout_ms);
switch (result) {
@ -163,7 +164,7 @@ X_STATUS XObject::SignalAndWait(XObject* signal_object, XObject* wait_object,
// Or X_STATUS_ALERTED?
return X_STATUS_USER_APC;
case xe::threading::WaitResult::kTimeout:
YieldProcessor();
xe::threading::MaybeYield();
return X_STATUS_TIMEOUT;
default:
case xe::threading::WaitResult::kAbandoned:
@ -188,8 +189,8 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
: std::chrono::milliseconds::max();
if (wait_type) {
auto result = xe::threading::WaitHandle::WaitAny(
std::move(wait_handles), alertable ? true : false, timeout_ms);
auto result = xe::threading::WaitAny(std::move(wait_handles),
alertable ? true : false, timeout_ms);
switch (result.first) {
case xe::threading::WaitResult::kSuccess:
return X_STATUS(result.second);
@ -197,7 +198,7 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
// Or X_STATUS_ALERTED?
return X_STATUS_USER_APC;
case xe::threading::WaitResult::kTimeout:
YieldProcessor();
xe::threading::MaybeYield();
return X_STATUS_TIMEOUT;
default:
case xe::threading::WaitResult::kAbandoned:
@ -206,8 +207,8 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
return X_STATUS_UNSUCCESSFUL;
}
} else {
auto result = xe::threading::WaitHandle::WaitAll(
std::move(wait_handles), alertable ? true : false, timeout_ms);
auto result = xe::threading::WaitAll(std::move(wait_handles),
alertable ? true : false, timeout_ms);
switch (result) {
case xe::threading::WaitResult::kSuccess:
return X_STATUS_SUCCESS;
@ -215,7 +216,7 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
// Or X_STATUS_ALERTED?
return X_STATUS_USER_APC;
case xe::threading::WaitResult::kTimeout:
YieldProcessor();
xe::threading::MaybeYield();
return X_STATUS_TIMEOUT;
default:
case xe::threading::WaitResult::kAbandoned: