[dawn][emscripten] Implements initial Future's API

- Implements the base features for Futures along with an implementation
  for instance.RequestAdapter.

Bug: 358445329
Change-Id: I8b1a7e30b4be682fa5b9d80d51cf9df5b6bdd834
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/202176
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/samples/SampleUtils.cpp b/src/dawn/samples/SampleUtils.cpp
index d53cf4b..499d90d 100644
--- a/src/dawn/samples/SampleUtils.cpp
+++ b/src/dawn/samples/SampleUtils.cpp
@@ -271,38 +271,41 @@
     // Create the instance
     sample->instance = wgpu::CreateInstance(nullptr);
 
-    // Create the adapter, device, and set the emscripten loop via callbacks
+    // Synchronously create the adapter
+    sample->instance.WaitAny(
+        sample->instance.RequestAdapter(
+            &adapterOptions, wgpu::CallbackMode::WaitAnyOnly,
+            [](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, const char* message) {
+                if (status != wgpu::RequestAdapterStatus::Success) {
+                    dawn::ErrorLog() << "Failed to get an adapter:" << message;
+                    return;
+                }
+                sample->adapter = std::move(adapter);
+            }),
+        UINT64_MAX);
+    if (sample->adapter == nullptr) {
+        return 1;
+    }
+
+    // Create the device and set the emscripten loop via callbacks
     // TODO(crbug.com/42241221) Update to use the newer APIs once they are implemented in
     // Emscripten.
-    sample->instance.RequestAdapter(
-        &adapterOptions,
-        [](WGPURequestAdapterStatus status, WGPUAdapter adapter, const char* message,
-           void* userdata) {
-            if (status != WGPURequestAdapterStatus_Success) {
-                dawn::ErrorLog() << "Failed to get an adapter:" << message;
+    wgpu::DeviceDescriptor deviceDesc = {};
+    sample->adapter.RequestDevice(
+        &deviceDesc,
+        [](WGPURequestDeviceStatus status, WGPUDevice device, const char* message, void* userdata) {
+            if (status != WGPURequestDeviceStatus_Success) {
+                dawn::ErrorLog() << "Failed to get an device:" << message;
                 return;
             }
-            sample->adapter = wgpu::Adapter::Acquire(adapter);
+            sample->device = wgpu::Device::Acquire(device);
+            sample->queue = sample->device.GetQueue();
 
-            wgpu::DeviceDescriptor deviceDesc = {};
-            sample->adapter.RequestDevice(
-                &deviceDesc,
-                [](WGPURequestDeviceStatus status, WGPUDevice device, const char* message,
-                   void* userdata) {
-                    if (status != WGPURequestDeviceStatus_Success) {
-                        dawn::ErrorLog() << "Failed to get an device:" << message;
-                        return;
-                    }
-                    sample->device = wgpu::Device::Acquire(device);
-                    sample->queue = sample->device.GetQueue();
-
-                    if (sample->Setup()) {
-                        emscripten_set_main_loop([]() { sample->FrameImpl(); }, 0, false);
-                    } else {
-                        dawn::ErrorLog() << "Failed to setup sample";
-                    }
-                },
-                nullptr);
+            if (sample->Setup()) {
+                emscripten_set_main_loop([]() { sample->FrameImpl(); }, 0, false);
+            } else {
+                dawn::ErrorLog() << "Failed to setup sample";
+            }
         },
         nullptr);
 #endif  // __EMSCRIPTEN__
diff --git a/src/emdawnwebgpu/CMakeLists.txt b/src/emdawnwebgpu/CMakeLists.txt
index 21bcfef..d545432 100644
--- a/src/emdawnwebgpu/CMakeLists.txt
+++ b/src/emdawnwebgpu/CMakeLists.txt
@@ -194,6 +194,8 @@
     target_link_options(emdawnwebgpu_config INTERFACE
         # We are using Dawn-generated bindings, not built-in ones
         "-sUSE_WEBGPU=0"
+        # We need Asyncify for Future implementation.
+        "-sASYNCIFY=1"
         # The JS libraries needed for bindings
         "--js-library=${EM_BUILD_GEN_DIR}/library_webgpu_enum_tables.js"
         "--js-library=${EM_BUILD_GEN_DIR}/library_webgpu_generated_struct_info.js"
diff --git a/third_party/emdawnwebgpu/library_webgpu.js b/third_party/emdawnwebgpu/library_webgpu.js
index f5d4926..ac3b0df 100644
--- a/third_party/emdawnwebgpu/library_webgpu.js
+++ b/third_party/emdawnwebgpu/library_webgpu.js
@@ -83,6 +83,23 @@
       WebGPU._table[ptr] = value;
     },
 
+    // Future to promise management, and temporary list allocated up-front for
+    // WaitAny implementation on the promises. Note that all FutureIDs
+    // (uint64_t) are passed either as a low and high value or by pointer
+    // because they need to be passed back and forth between JS and C++, and JS
+    // is currently unable to pass a value to a C++ function as a uint64_t.
+    // This might be possible with -sWASM_BIGINT, but I was unable to get that
+    // to work properly at the time of writing.
+    _futures: [],
+    _futureInsert: (futureIdL, futureIdH, promise) => {
+#if ASYNCIFY
+      var futureId = futureIdH * 0x100000000 + futureIdL;
+      WebGPU._futures[futureId] =
+        new Promise((resolve) => promise.finally(resolve(futureId)));
+#endif
+    },
+    _waitAnyPromisesList: [],
+
     errorCallback: (callback, type, message, userdata) => {
       var sp = stackSave();
       var messagePtr = stringToUTF8OnStack(message);
@@ -452,13 +469,46 @@
   },
 
   // ----------------------------------------------------------------------------
-  // Definitions for JS emwgpu functions (callable from webgpu.cpp)
+  // Definitions for standalone JS emwgpu functions (callable from webgpu.cpp)
   // ----------------------------------------------------------------------------
 
   emwgpuDelete: (id) => {
     delete WebGPU._table[id];
   },
 
+#if ASYNCIFY
+  // Returns a FutureID that was resolved, or kNullFutureId if timed out.
+  emwgpuWaitAny__async: true,
+  emwgpuWaitAny: (futurePtr, futureCount, timeoutNSPtr) => {
+    var promises = WebGPU._waitAnyPromisesList;
+    if (timeoutNSPtr) {
+      var timeoutMS = {{{ gpu.makeGetU64('timeoutNSPtr', 0) }}} / 1000000;
+      promises.length = futureCount + 1;
+      promise[futureCount] = new Promise((resolve) => setTimeout(resolve, timeoutMS, 0));
+    } else {
+      promises.length = futureCount;
+    }
+
+    for (var i = 0; i < futureCount; ++i) {
+      // If any of the FutureIDs are not tracked, it means it must be done.
+      var futureId = {{{ gpu.makeGetU64('(futurePtr + i * 8)', 0) }}};
+      if (!(futureId in WebGPU._futures)) {
+        return futureId;
+      }
+      promises[i] = WebGPU._futures[futureId];
+    }
+
+    var result = Asyncify.handleAsync(async () => {
+      return await Promise.race(promises);
+    });
+
+    // Clean up internal futures state.
+    delete WebGPU._futures[result];
+    WebGPU._waitAnyPromisesList.length = 0;
+    return result;
+  },
+#endif
+
   // --------------------------------------------------------------------------
   // WebGPU function definitions, with methods organized by "class".
   //
@@ -1817,15 +1867,9 @@
     return navigator["gpu"]["wgslLanguageFeatures"].has(WebGPU.WGSLFeatureName[featureEnumValue]);
   },
 
-  wgpuInstanceProcessEvents: (instance) => {
-    // TODO: This could probably be emulated with ASYNCIFY.
-#if ASSERTIONS
-    abort('wgpuInstanceProcessEvents is unsupported (use requestAnimationFrame via html5.h instead)');
-#endif
-  },
-
-  wgpuInstanceRequestAdapter__deps: ['$callUserCallback', '$stringToUTF8OnStack', 'emwgpuCreateAdapter'],
-  wgpuInstanceRequestAdapter: (instancePtr, options, callback, userdata) => {
+  emwgpuInstanceRequestAdapter__i53abi: false,
+  emwgpuInstanceRequestAdapter__deps: ['$callUserCallback', '$stringToUTF8OnStack', 'emwgpuCreateAdapter', 'emwgpuOnRequestAdapterCompleted'],
+  emwgpuInstanceRequestAdapter: (instancePtr, futureIdL, futureIdH, options) => {
     var opts;
     if (options) {
       {{{ gpu.makeCheckDescriptor('options') }}}
@@ -1838,37 +1882,35 @@
     }
 
     if (!('gpu' in navigator)) {
-      var sp = stackSave();
-      var messagePtr = stringToUTF8OnStack('WebGPU not available on this browser (navigator.gpu is not available)');
-      {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr, userdata);
-      stackRestore(sp);
+      withStackSave(() => {
+        var messagePtr = stringToUTF8OnStack('WebGPU not available on this browser (navigator.gpu is not available)');
+        _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr);
+      });
       return;
     }
 
     {{{ runtimeKeepalivePush() }}}
-    navigator["gpu"]["requestAdapter"](opts).then((adapter) => {
+    WebGPU._futureInsert(futureIdL, futureIdH, navigator["gpu"]["requestAdapter"](opts).then((adapter) => {
       {{{ runtimeKeepalivePop() }}}
-      callUserCallback(() => {
-        if (adapter) {
-          var adapterPtr = _emwgpuCreateAdapter();
-          WebGPU._tableInsert(adapterPtr, adapter);
-          {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestAdapterStatus.Success }}}, adapterPtr, 0, userdata);
-        } else {
-          var sp = stackSave();
-          var messagePtr = stringToUTF8OnStack('WebGPU not available on this system (requestAdapter returned null)');
-          {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr, userdata);
-          stackRestore(sp);
-        }
-      });
+      if (adapter) {
+        var adapterPtr = _emwgpuCreateAdapter();
+        WebGPU._tableInsert(adapterPtr, adapter);
+        _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Success }}}, adapterPtr, 0);
+      } else {
+        withStackSave(() => {
+          var messagePtr = stringToUTF8OnStack('WebGPU not available on this browser (requestAdapter returned null)');
+          _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr);
+        });
+      }
+      return;
     }, (ex) => {
       {{{ runtimeKeepalivePop() }}}
-      callUserCallback(() => {
-        var sp = stackSave();
+      withStackSave(() => {
         var messagePtr = stringToUTF8OnStack(ex.message);
-        {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestAdapterStatus.Error }}}, 0, messagePtr, userdata);
-        stackRestore(sp);
+        _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Error }}}, 0, messagePtr);
       });
-    });
+      return;
+    }));
   },
 
   // --------------------------------------------------------------------------
@@ -2485,7 +2527,9 @@
 
 for (const key of Object.keys(LibraryWebGPU)) {
   if (typeof LibraryWebGPU[key] === 'function') {
-    LibraryWebGPU[key + '__i53abi'] = true;
+    if (!(key + '__i53abi' in LibraryWebGPU)) {
+      LibraryWebGPU[key + '__i53abi'] = true;
+    }
   }
 }
 
diff --git a/third_party/emdawnwebgpu/webgpu.cpp b/third_party/emdawnwebgpu/webgpu.cpp
index 6a77b26..4572b67 100644
--- a/third_party/emdawnwebgpu/webgpu.cpp
+++ b/third_party/emdawnwebgpu/webgpu.cpp
@@ -7,18 +7,42 @@
 // This file and library_webgpu.js together implement <webgpu/webgpu.h>.
 //
 
+#include <emscripten/emscripten.h>
 #include <webgpu/webgpu.h>
 
 #include <array>
 #include <atomic>
 #include <cassert>
 #include <cstdlib>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <set>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+using FutureID = uint64_t;
+static constexpr FutureID kNullFutureId = 0;
+using InstanceID = uint64_t;
 
 // ----------------------------------------------------------------------------
 // Declarations for JS emwgpu functions (defined in library_webgpu.js)
 // ----------------------------------------------------------------------------
 extern "C" {
 void emwgpuDelete(void* id);
+
+// Note that for the JS entry points, we pass uint64_t as pointer and decode it
+// on the other side.
+FutureID emwgpuWaitAny(FutureID const* futurePtr,
+                       size_t futureCount,
+                       uint64_t const* timeoutNSPtr);
+
+// Future/async operation that need to be forwarded to JS.
+void emwgpuInstanceRequestAdapter(WGPUInstance instance,
+                                  FutureID futureId,
+                                  const WGPURequestAdapterOptions* options);
 }  // extern "C"
 
 // ----------------------------------------------------------------------------
@@ -38,7 +62,17 @@
   void operator=(const NonCopyable&) = delete;
 };
 
-class RefCounted : public NonCopyable {
+class NonMovable : NonCopyable {
+ protected:
+  constexpr NonMovable() = default;
+  ~NonMovable() = default;
+
+ private:
+  NonMovable(NonMovable&&) = delete;
+  void operator=(NonMovable&&) = delete;
+};
+
+class RefCounted : NonMovable {
  public:
   RefCounted() = default;
   virtual ~RefCounted() = default;
@@ -111,13 +145,354 @@
   X(TextureView)
 // clang-format on
 
+// ----------------------------------------------------------------------------
+// Future related structures and helpers.
+// ----------------------------------------------------------------------------
+
+enum class EventCompletionType {
+  Ready,
+  Shutdown,
+};
+enum class EventType {
+  RequestAdapter,
+};
+
+class EventManager;
+class TrackedEvent;
+
+class TrackedEvent : NonMovable {
+ public:
+  virtual ~TrackedEvent() = default;
+  virtual EventType GetType() = 0;
+  virtual void Complete(FutureID futureId, EventCompletionType type) = 0;
+
+ protected:
+  TrackedEvent(InstanceID instance, WGPUCallbackMode mode)
+      : mInstanceId(instance), mMode(mode) {}
+
+ private:
+  friend class EventManager;
+
+  // Events need to keep track of the instance they came from for validation.
+  const InstanceID mInstanceId;
+  const WGPUCallbackMode mMode;
+  bool mIsReady = false;
+};
+
+// Thread-safe EventManager class that tracks all events.
+//
+// Note that there is a single global EventManager that should be accessed via
+// GetEventManager(). The EventManager needs to outlive all WGPUInstances in
+// order to handle Spontaneous events.
+class EventManager : NonMovable {
+ public:
+  void RegisterInstance(InstanceID instance) {
+    assert(instance);
+    std::unique_lock<std::mutex> lock(mMutex);
+    mPerInstanceEvents.try_emplace(instance);
+  }
+
+  void UnregisterInstance(InstanceID instance) {
+    assert(instance);
+    std::unique_lock<std::mutex> lock(mMutex);
+    auto it = mPerInstanceEvents.find(instance);
+    assert(it != mPerInstanceEvents.end());
+
+    // When unregistering the Instance, resolve all non-spontaneous callbacks
+    // with Shutdown.
+    for (const FutureID futureId : it->second) {
+      if (auto it = mEvents.find(futureId); it != mEvents.end()) {
+        it->second->Complete(futureId, EventCompletionType::Shutdown);
+        mEvents.erase(it);
+      }
+    }
+    mPerInstanceEvents.erase(instance);
+  }
+
+  void ProcessEvents(InstanceID instance) {
+    assert(instance);
+    std::vector<std::pair<FutureID, std::unique_ptr<TrackedEvent>>> completable;
+    {
+      std::unique_lock<std::mutex> lock(mMutex);
+      auto instanceIt = mPerInstanceEvents.find(instance);
+      assert(instanceIt != mPerInstanceEvents.end());
+      auto& instanceFutureIds = instanceIt->second;
+
+      // Note that we are only currently handling AllowProcessEvents events,
+      // i.e. we are not handling AllowSpontaneous events in this loop.
+      for (auto futureIdsIt = instanceFutureIds.begin();
+           futureIdsIt != instanceFutureIds.end();) {
+        FutureID futureId = *futureIdsIt;
+        auto eventIt = mEvents.find(futureId);
+        if (eventIt == mEvents.end()) {
+          ++futureIdsIt;
+          continue;
+        }
+        auto& event = eventIt->second;
+
+        if (event->mMode == WGPUCallbackMode_AllowProcessEvents &&
+            event->mIsReady) {
+          completable.emplace_back(futureId, std::move(event));
+          mEvents.erase(eventIt);
+          futureIdsIt = instanceFutureIds.erase(futureIdsIt);
+        } else {
+          ++futureIdsIt;
+        }
+      }
+    }
+
+    // Since the sets are ordered, the events must already be ordered by
+    // FutureID.
+    for (auto& [futureId, event] : completable) {
+      event->Complete(futureId, EventCompletionType::Ready);
+    }
+  }
+
+  WGPUWaitStatus WaitAny(InstanceID instance,
+                         size_t count,
+                         WGPUFutureWaitInfo* infos,
+                         uint64_t timeoutNS) {
+    assert(instance);
+
+    if (count == 0) {
+      return WGPUWaitStatus_Success;
+    }
+
+    // To handle timeouts, use Asyncify and proxy back into JS.
+    if (timeoutNS > 0) {
+      // Cannot handle timeouts if we are not using Asyncify.
+      if (!emscripten_has_asyncify()) {
+        return WGPUWaitStatus_UnsupportedTimeout;
+      }
+
+      std::vector<FutureID> futures;
+      std::unordered_map<FutureID, WGPUFutureWaitInfo*> futureIdToInfo;
+      for (size_t i = 0; i < count; ++i) {
+        futures.push_back(infos[i].future.id);
+        futureIdToInfo.emplace(infos[i].future.id, &infos[i]);
+      }
+
+      bool hasTimeout = timeoutNS != UINT64_MAX;
+      FutureID completedId = emwgpuWaitAny(futures.data(), count,
+                                           hasTimeout ? &timeoutNS : nullptr);
+      if (completedId == kNullFutureId) {
+        return WGPUWaitStatus_TimedOut;
+      }
+      futureIdToInfo[completedId]->completed = true;
+
+      std::unique_ptr<TrackedEvent> completed;
+      {
+        std::unique_lock<std::mutex> lock(mMutex);
+        auto eventIt = mEvents.find(completedId);
+        if (eventIt == mEvents.end()) {
+          return WGPUWaitStatus_Success;
+        }
+
+        completed = std::move(eventIt->second);
+        mEvents.erase(eventIt);
+        if (auto instanceIt = mPerInstanceEvents.find(instance);
+            instanceIt != mPerInstanceEvents.end()) {
+          instanceIt->second.erase(completedId);
+        }
+      }
+
+      if (completed) {
+        completed->Complete(completedId, EventCompletionType::Ready);
+      }
+      return WGPUWaitStatus_Success;
+    }
+
+    std::map<FutureID, std::unique_ptr<TrackedEvent>> completable;
+    bool anyCompleted = false;
+    {
+      std::unique_lock<std::mutex> lock(mMutex);
+      auto instanceIt = mPerInstanceEvents.find(instance);
+      assert(instanceIt != mPerInstanceEvents.end());
+      auto& instanceFutureIds = instanceIt->second;
+
+      for (size_t i = 0; i < count; ++i) {
+        FutureID futureId = infos[i].future.id;
+        auto eventIt = mEvents.find(futureId);
+        if (eventIt == mEvents.end()) {
+          infos[i].completed = true;
+          continue;
+        }
+
+        auto& event = eventIt->second;
+        assert(event->mInstanceId == instance);
+        infos[i].completed = event->mIsReady;
+        if (event->mIsReady) {
+          anyCompleted = true;
+          completable.emplace(futureId, std::move(event));
+          mEvents.erase(eventIt);
+          instanceFutureIds.erase(futureId);
+        }
+      }
+    }
+
+    // We used an ordered map to collect the events, so they must be ordered.
+    for (auto& [futureId, event] : completable) {
+      event->Complete(futureId, EventCompletionType::Ready);
+    }
+    return anyCompleted ? WGPUWaitStatus_Success : WGPUWaitStatus_TimedOut;
+  }
+
+  std::pair<FutureID, bool> TrackEvent(std::unique_ptr<TrackedEvent> event) {
+    FutureID futureId = mNextFutureId++;
+    InstanceID instance = event->mInstanceId;
+    std::unique_lock<std::mutex> lock(mMutex);
+    switch (event->mMode) {
+      case WGPUCallbackMode_WaitAnyOnly:
+      case WGPUCallbackMode_AllowProcessEvents: {
+        auto it = mPerInstanceEvents.find(instance);
+        if (it == mPerInstanceEvents.end()) {
+          // The instance has already been unregistered so just complete this
+          // event as shutdown now.
+          event->Complete(futureId, EventCompletionType::Shutdown);
+          return {futureId, false};
+        }
+        it->second.insert(futureId);
+        mEvents.try_emplace(futureId, std::move(event));
+        break;
+      }
+      case WGPUCallbackMode_AllowSpontaneous: {
+        mEvents.try_emplace(futureId, std::move(event));
+        break;
+      }
+      default: {
+        // Invalid callback mode, so we just return kNullFutureId.
+        return {kNullFutureId, false};
+      }
+    }
+    return {futureId, true};
+  }
+
+  template <typename Event, typename... ReadyArgs>
+  void SetFutureReady(FutureID futureId, ReadyArgs&&... readyArgs) {
+    std::unique_ptr<TrackedEvent> spontaneousEvent;
+    {
+      std::unique_lock<std::mutex> lock(mMutex);
+      auto eventIt = mEvents.find(futureId);
+      if (eventIt == mEvents.end()) {
+        return;
+      }
+
+      auto& event = eventIt->second;
+      assert(event->GetType() == Event::kType);
+      static_cast<Event*>(event.get())
+          ->ReadyHook(std::forward<ReadyArgs>(readyArgs)...);
+      event->mIsReady = true;
+
+      // If the event can be spontaneously completed, prepare to do so now.
+      if (event->mMode == WGPUCallbackMode_AllowSpontaneous) {
+        spontaneousEvent = std::move(event);
+        mEvents.erase(futureId);
+      }
+    }
+
+    if (spontaneousEvent) {
+      spontaneousEvent->Complete(futureId, EventCompletionType::Ready);
+    }
+  }
+
+ private:
+  std::mutex mMutex;
+  std::atomic<FutureID> mNextFutureId = 1;
+
+  // The EventManager separates events based on the WGPUInstance that the event
+  // stems from.
+  std::unordered_map<InstanceID, std::set<FutureID>> mPerInstanceEvents;
+  std::unordered_map<FutureID, std::unique_ptr<TrackedEvent>> mEvents;
+};
+
+static EventManager& GetEventManager() {
+  static EventManager kEventManager;
+  return kEventManager;
+}
+
+// ----------------------------------------------------------------------------
+// Future events.
+// ----------------------------------------------------------------------------
+
+class RequestAdapterEvent final : public TrackedEvent {
+ public:
+  static constexpr EventType kType = EventType::RequestAdapter;
+
+  RequestAdapterEvent(InstanceID instance,
+                      const WGPURequestAdapterCallbackInfo2& callbackInfo)
+      : TrackedEvent(instance, callbackInfo.mode),
+        mCallback(callbackInfo.callback),
+        mUserdata1(callbackInfo.userdata1),
+        mUserdata2(callbackInfo.userdata2) {}
+
+  EventType GetType() override { return kType; }
+
+  void ReadyHook(WGPURequestAdapterStatus status,
+                 WGPUAdapter adapter,
+                 const char* message) {
+    mStatus = status;
+    mAdapter = adapter;
+    mMessage = message;
+  }
+
+  void Complete(FutureID futureId, EventCompletionType type) override {
+    if (type == EventCompletionType::Shutdown) {
+      mStatus = WGPURequestAdapterStatus_InstanceDropped;
+      mMessage = "A valid external Instance reference no longer exists.";
+    }
+    if (mCallback) {
+      mCallback(
+          mStatus,
+          mStatus == WGPURequestAdapterStatus_Success ? mAdapter : nullptr,
+          mMessage ? mMessage->c_str() : nullptr, mUserdata1, mUserdata2);
+    }
+  }
+
+ private:
+  WGPURequestAdapterCallback2 mCallback = nullptr;
+  void* mUserdata1 = nullptr;
+  void* mUserdata2 = nullptr;
+
+  WGPURequestAdapterStatus mStatus;
+  WGPUAdapter mAdapter = nullptr;
+  std::optional<std::string> mMessage = std::nullopt;
+};
+
+// ----------------------------------------------------------------------------
+// WGPU struct implementations.
+// ----------------------------------------------------------------------------
+
 // Default struct implementations.
 #define DEFINE_WGPU_DEFAULT_STRUCT(Name) \
   struct WGPU##Name##Impl : public RefCounted {};
 WGPU_PASSTHROUGH_OBJECTS(DEFINE_WGPU_DEFAULT_STRUCT)
 
-// Specialized struct implementations
-struct WGPUInstanceImpl : public RefCounted {};
+// Instance is specially implemented in order to handle Futures implementation.
+struct WGPUInstanceImpl : public RefCounted {
+ public:
+  WGPUInstanceImpl() {
+    mId = GetNextInstanceId();
+    GetEventManager().RegisterInstance(mId);
+  }
+  ~WGPUInstanceImpl() override { GetEventManager().UnregisterInstance(mId); }
+  InstanceID GetId() const { return mId; }
+
+  void ProcessEvents() { GetEventManager().ProcessEvents(mId); }
+
+  WGPUWaitStatus WaitAny(size_t count,
+                         WGPUFutureWaitInfo* infos,
+                         uint64_t timeoutNS) {
+    return GetEventManager().WaitAny(mId, count, infos, timeoutNS);
+  }
+
+ private:
+  static InstanceID GetNextInstanceId() {
+    static std::atomic<InstanceID> kNextInstanceId = 1;
+    return kNextInstanceId++;
+  }
+
+  InstanceID mId;
+};
 
 // Device is specially implemented in order to handle refcounting the Queue.
 struct WGPUDeviceImpl : public RefCounted {
@@ -152,6 +527,15 @@
   return new WGPUDeviceImpl(queue);
 }
 
+// Future event callbacks.
+void emwgpuOnRequestAdapterCompleted(FutureID futureId,
+                                     WGPURequestAdapterStatus status,
+                                     WGPUAdapter adapter,
+                                     const char* message) {
+  GetEventManager().SetFutureReady<RequestAdapterEvent>(futureId, status,
+                                                        adapter, message);
+}
+
 }  // extern "C"
 
 // ----------------------------------------------------------------------------
@@ -241,6 +625,48 @@
 // Methods of Instance
 // ----------------------------------------------------------------------------
 
+void wgpuInstanceProcessEvents(WGPUInstance instance) {
+  instance->ProcessEvents();
+}
+
+void wgpuInstanceRequestAdapter(WGPUInstance instance,
+                                WGPURequestAdapterOptions const* options,
+                                WGPURequestAdapterCallback callback,
+                                void* userdata) {
+  WGPURequestAdapterCallbackInfo2 callbackInfo = {};
+  callbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
+  callbackInfo.callback = [](WGPURequestAdapterStatus status,
+                             WGPUAdapter adapter, char const* message,
+                             void* callback, void* userdata) {
+    auto cb = reinterpret_cast<WGPURequestAdapterCallback>(callback);
+    cb(status, adapter, message, userdata);
+  };
+  callbackInfo.userdata1 = reinterpret_cast<void*>(callback);
+  callbackInfo.userdata2 = userdata;
+  wgpuInstanceRequestAdapter2(instance, options, callbackInfo);
+}
+
+WGPUFuture wgpuInstanceRequestAdapter2(
+    WGPUInstance instance,
+    WGPURequestAdapterOptions const* options,
+    WGPURequestAdapterCallbackInfo2 callbackInfo) {
+  auto [futureId, tracked] = GetEventManager().TrackEvent(
+      std::make_unique<RequestAdapterEvent>(instance->GetId(), callbackInfo));
+  if (!tracked) {
+    return WGPUFuture{kNullFutureId};
+  }
+
+  emwgpuInstanceRequestAdapter(instance, futureId, options);
+  return WGPUFuture{futureId};
+}
+
+WGPUWaitStatus wgpuInstanceWaitAny(WGPUInstance instance,
+                                   size_t futureCount,
+                                   WGPUFutureWaitInfo* futures,
+                                   uint64_t timeoutNS) {
+  return instance->WaitAny(futureCount, futures, timeoutNS);
+}
+
 // ----------------------------------------------------------------------------
 // Methods of PipelineLayout
 // ----------------------------------------------------------------------------