blob: f56707829d9014fed61eaee2d6b216ea1c3fd5ce [file] [log] [blame]
// Copyright 2023 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <unordered_map>
#include <utility>
#include <vector>
#include "dawn/wire/ObjectHandle.h"
#include "dawn/wire/client/Client.h"
#include "dawn/wire/client/EventManager.h"
namespace dawn::wire::client {
// EventManager
EventManager::EventManager(Client* client) : mClient(client) {}
std::pair<FutureID, bool> EventManager::TrackEvent(WGPUCallbackMode mode,
EventCallback&& callback) {
FutureID futureID = mNextFutureID++;
if (mClient->IsDisconnected()) {
callback(EventCompletionType::Shutdown);
return {futureID, false};
}
mTrackedEvents.Use([&](auto trackedEvents) {
auto [it, inserted] =
trackedEvents->emplace(futureID, TrackedEvent(mode, std::move(callback)));
DAWN_ASSERT(inserted);
});
return {futureID, true};
}
void EventManager::ShutDown() {
// Call any outstanding callbacks before destruction.
while (true) {
std::unordered_map<FutureID, TrackedEvent> movedEvents;
mTrackedEvents.Use([&](auto trackedEvents) { movedEvents = std::move(*trackedEvents); });
if (movedEvents.empty()) {
break;
}
for (auto& [futureID, trackedEvent] : movedEvents) {
// Event should be already marked Ready since events are actually driven by
// RequestTrackers (at the time of this writing), which all shut down before this.
DAWN_ASSERT(trackedEvent.mReady);
trackedEvent.mCallback(EventCompletionType::Shutdown);
trackedEvent.mCallback = nullptr;
}
}
}
void EventManager::SetFutureReady(FutureID futureID) {
DAWN_ASSERT(futureID > 0);
mTrackedEvents.Use([&](auto trackedEvents) {
TrackedEvent& trackedEvent = trackedEvents->at(futureID); // Asserts futureID is in the map
trackedEvent.mReady = true;
});
// TODO(crbug.com/dawn/2059): Handle spontaneous completions.
}
void EventManager::ProcessPollEvents() {
std::vector<TrackedEvent> eventsToCompleteNow;
// TODO(crbug.com/dawn/2060): EventManager shouldn't bother to track ProcessEvents-type events
// until they've completed. We can queue them up when they're received on the wire. (Before that
// point, the RequestTracker tracks them. If/when we merge this with RequestTracker, then we'll
// track both here but still don't need to queue them for ProcessEvents until they complete.)
mTrackedEvents.Use([&](auto trackedEvents) {
for (auto it = trackedEvents->begin(); it != trackedEvents->end();) {
TrackedEvent& event = it->second;
bool shouldRemove = (event.mMode == WGPUCallbackMode_AllowProcessEvents ||
event.mMode == WGPUCallbackMode_AllowSpontaneous) &&
event.mReady;
if (!shouldRemove) {
++it;
continue;
}
// mCallback may still be null if it's stale (was already spontaneously completed).
if (event.mCallback) {
eventsToCompleteNow.emplace_back(std::move(event));
}
it = trackedEvents->erase(it);
}
});
for (TrackedEvent& event : eventsToCompleteNow) {
DAWN_ASSERT(event.mReady && event.mCallback);
event.mCallback(EventCompletionType::Ready);
event.mCallback = nullptr;
}
}
WGPUWaitStatus EventManager::WaitAny(size_t count, WGPUFutureWaitInfo* infos, uint64_t timeoutNS) {
// Validate for feature support.
if (timeoutNS > 0) {
// Wire doesn't support timedWaitEnable (for now). (There's no UnsupportedCount or
// UnsupportedMixedSources validation here, because those only apply to timed waits.)
//
// TODO(crbug.com/dawn/1987): CreateInstance needs to validate timedWaitEnable was false.
return WGPUWaitStatus_UnsupportedTimeout;
}
if (count == 0) {
return WGPUWaitStatus_Success;
}
std::vector<TrackedEvent> eventsToCompleteNow;
bool anyCompleted = false;
const FutureID firstInvalidFutureID = mNextFutureID;
mTrackedEvents.Use([&](auto trackedEvents) {
for (size_t i = 0; i < count; ++i) {
FutureID futureID = infos[i].future.id;
DAWN_ASSERT(futureID < firstInvalidFutureID);
auto it = trackedEvents->find(futureID);
if (it == trackedEvents->end()) {
infos[i].completed = true;
anyCompleted = true;
continue;
}
TrackedEvent& event = it->second;
// Early update .completed, in prep to complete the callback if ready.
infos[i].completed = event.mReady;
if (event.mReady) {
anyCompleted = true;
if (event.mCallback) {
eventsToCompleteNow.emplace_back(std::move(event));
}
trackedEvents->erase(it);
}
}
});
// TODO(crbug.com/dawn/2066): Guarantee the event ordering from the JS spec.
for (TrackedEvent& event : eventsToCompleteNow) {
DAWN_ASSERT(event.mReady && event.mCallback);
// .completed has already been set to true (before the callback, per API contract).
event.mCallback(EventCompletionType::Ready);
event.mCallback = nullptr;
}
return anyCompleted ? WGPUWaitStatus_Success : WGPUWaitStatus_TimedOut;
}
// EventManager::TrackedEvent
EventManager::TrackedEvent::TrackedEvent(WGPUCallbackMode mode, EventCallback&& callback)
: mMode(mode), mCallback(callback) {}
EventManager::TrackedEvent::~TrackedEvent() {
// Make sure we're not dropping a callback on the floor.
DAWN_ASSERT(!mCallback);
}
} // namespace dawn::wire::client