dawn::wire::client: Merge object allocators, add variadic Make.

This commit changes all the [Object]Allocators from the Client into a
PerType<ObjectStore> member that contains a bunch of ObjectStore acting
on ObjectBase.

Adds a new (template) member functions to the client, Make/Get/Free that
act on any object type, and update all the uses of previous
[Object]Allocator to use these new methods.

Also removes generated code that was generated per object type in favor
of using the type-generic ObjectAllocator.

Bug: dawn:1451
Change-Id: I6463b2fc4a827e3000c2a666abf08aa1a71c3b3b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/93141
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/generator/templates/dawn/wire/client/ApiProcs.cpp b/generator/templates/dawn/wire/client/ApiProcs.cpp
index ef46293..85fe15d 100644
--- a/generator/templates/dawn/wire/client/ApiProcs.cpp
+++ b/generator/templates/dawn/wire/client/ApiProcs.cpp
@@ -58,7 +58,7 @@
 
                     //* For object creation, store the object ID the client will use for the result.
                     {% if method.return_type.category == "object" %}
-                        auto* returnObject = self->GetClient()->{{method.return_type.name.CamelCase()}}Allocator().New(self->GetClient());
+                        auto* returnObject = self->GetClient()->Make<{{method.return_type.name.CamelCase()}}>();
                         cmd.result = returnObject->GetWireHandle();
                     {% endif %}
 
@@ -97,7 +97,7 @@
 
             Client* client = obj->GetClient();
             client->SerializeCommand(cmd);
-            client->{{type.name.CamelCase()}}Allocator().Free(obj);
+            client->Free(obj);
         }
 
         void Client{{as_MethodSuffix(type.name, Name("reference"))}}({{cType}} cObj) {
diff --git a/generator/templates/dawn/wire/client/ClientBase.h b/generator/templates/dawn/wire/client/ClientBase.h
index 2fb1e5d..03d08e9 100644
--- a/generator/templates/dawn/wire/client/ClientBase.h
+++ b/generator/templates/dawn/wire/client/ClientBase.h
@@ -18,7 +18,6 @@
 #include "dawn/wire/ChunkedCommandHandler.h"
 #include "dawn/wire/WireCmd_autogen.h"
 #include "dawn/wire/client/ApiObjects.h"
-#include "dawn/wire/client/ObjectAllocator.h"
 
 namespace dawn::wire::client {
 
@@ -27,25 +26,6 @@
         ClientBase() = default;
         ~ClientBase() override = default;
 
-        {% for type in by_category["object"] %}
-            const ObjectAllocator<{{type.name.CamelCase()}}>& {{type.name.CamelCase()}}Allocator() const {
-                return m{{type.name.CamelCase()}}Allocator;
-            }
-            ObjectAllocator<{{type.name.CamelCase()}}>& {{type.name.CamelCase()}}Allocator() {
-                return m{{type.name.CamelCase()}}Allocator;
-            }
-        {% endfor %}
-
-        void FreeObject(ObjectType objectType, ObjectBase* obj) {
-            switch (objectType) {
-                {% for type in by_category["object"] %}
-                    case ObjectType::{{type.name.CamelCase()}}:
-                        m{{type.name.CamelCase()}}Allocator.Free(static_cast<{{type.name.CamelCase()}}*>(obj));
-                        break;
-                {% endfor %}
-            }
-        }
-
       private:
         // Implementation of the ObjectIdProvider interface
         {% for type in by_category["object"] %}
@@ -63,10 +43,6 @@
                 return WireResult::Success;
             }
         {% endfor %}
-
-        {% for type in by_category["object"] %}
-            ObjectAllocator<{{type.name.CamelCase()}}> m{{type.name.CamelCase()}}Allocator;
-        {% endfor %}
     };
 
 }  // namespace dawn::wire::client
diff --git a/generator/templates/dawn/wire/client/ClientHandlers.cpp b/generator/templates/dawn/wire/client/ClientHandlers.cpp
index f682c34..d09c0f5 100644
--- a/generator/templates/dawn/wire/client/ClientHandlers.cpp
+++ b/generator/templates/dawn/wire/client/ClientHandlers.cpp
@@ -21,7 +21,7 @@
     {% for command in cmd_records["return command"] %}
         bool Client::Handle{{command.name.CamelCase()}}(DeserializeBuffer* deserializeBuffer) {
             Return{{command.name.CamelCase()}}Cmd cmd;
-            WireResult deserializeResult = cmd.Deserialize(deserializeBuffer, &mAllocator);
+            WireResult deserializeResult = cmd.Deserialize(deserializeBuffer, &mWireCommandAllocator);
 
             if (deserializeResult == WireResult::FatalError) {
                 return false;
@@ -32,7 +32,7 @@
                 {% set name = as_varName(member.name) %}
 
                 {% if member.type.dict_name == "ObjectHandle" %}
-                    {{Type}}* {{name}} = {{Type}}Allocator().GetObject(cmd.{{name}}.id);
+                    {{Type}}* {{name}} = Get<{{Type}}>(cmd.{{name}}.id);
                     if ({{name}} != nullptr && {{name}}->GetWireGeneration() != cmd.{{name}}.generation) {
                         {{name}} = nullptr;
                     }
@@ -84,7 +84,7 @@
             if (!success) {
                 return nullptr;
             }
-            mAllocator.Reset();
+            mWireCommandAllocator.Reset();
         }
 
         if (deserializeBuffer.AvailableSize() != 0) {
diff --git a/src/dawn/wire/BUILD.gn b/src/dawn/wire/BUILD.gn
index 5b26626..56aa038 100644
--- a/src/dawn/wire/BUILD.gn
+++ b/src/dawn/wire/BUILD.gn
@@ -90,9 +90,10 @@
     "client/Instance.h",
     "client/LimitsAndFeatures.cpp",
     "client/LimitsAndFeatures.h",
-    "client/ObjectAllocator.h",
     "client/ObjectBase.cpp",
     "client/ObjectBase.h",
+    "client/ObjectStore.cpp",
+    "client/ObjectStore.h",
     "client/QuerySet.cpp",
     "client/QuerySet.h",
     "client/Queue.cpp",
diff --git a/src/dawn/wire/CMakeLists.txt b/src/dawn/wire/CMakeLists.txt
index 9a79484..c470cea 100644
--- a/src/dawn/wire/CMakeLists.txt
+++ b/src/dawn/wire/CMakeLists.txt
@@ -63,7 +63,8 @@
     "client/Instance.h"
     "client/LimitsAndFeatures.cpp"
     "client/LimitsAndFeatures.h"
-    "client/ObjectAllocator.h"
+    "client/ObjectStore.cpp"
+    "client/ObjectStore.h"
     "client/ObjectBase.cpp"
     "client/ObjectBase.h"
     "client/QuerySet.cpp"
diff --git a/src/dawn/wire/client/Adapter.cpp b/src/dawn/wire/client/Adapter.cpp
index ceab7b0..6edf63c 100644
--- a/src/dawn/wire/client/Adapter.cpp
+++ b/src/dawn/wire/client/Adapter.cpp
@@ -71,7 +71,7 @@
         return;
     }
 
-    Device* device = client->DeviceAllocator().New(client);
+    Device* device = client->Make<Device>();
     uint64_t serial = mRequestDeviceRequests.Add({callback, device->GetWireId(), userdata});
 
     AdapterRequestDeviceCmd cmd;
@@ -110,12 +110,12 @@
     }
 
     Client* client = GetClient();
-    Device* device = client->DeviceAllocator().GetObject(request.deviceObjectId);
+    Device* device = client->Get<Device>(request.deviceObjectId);
 
     // If the return status is a failure we should give a null device to the callback and
     // free the allocation.
     if (status != WGPURequestDeviceStatus_Success) {
-        client->DeviceAllocator().Free(device);
+        client->Free(device);
         request.callback(status, nullptr, message, request.userdata);
         return true;
     }
diff --git a/src/dawn/wire/client/Buffer.cpp b/src/dawn/wire/client/Buffer.cpp
index b66e49b..aaf6282 100644
--- a/src/dawn/wire/client/Buffer.cpp
+++ b/src/dawn/wire/client/Buffer.cpp
@@ -74,7 +74,7 @@
     // Create the buffer and send the creation command.
     // This must happen after any potential device->CreateErrorBuffer()
     // as server expects allocating ids to be monotonically increasing
-    Buffer* buffer = wireClient->BufferAllocator().New(wireClient);
+    Buffer* buffer = wireClient->Make<Buffer>();
     buffer->mDevice = device;
     buffer->mDeviceIsAlive = device->GetAliveWeakPtr();
     buffer->mSize = descriptor->size;
@@ -127,7 +127,7 @@
 WGPUBuffer Buffer::CreateError(Device* device, const WGPUBufferDescriptor* descriptor) {
     Client* client = device->GetClient();
 
-    Buffer* buffer = client->BufferAllocator().New(client);
+    Buffer* buffer = client->Make<Buffer>();
     buffer->mDevice = device;
     buffer->mDeviceIsAlive = device->GetAliveWeakPtr();
     buffer->mSize = descriptor->size;
diff --git a/src/dawn/wire/client/Client.cpp b/src/dawn/wire/client/Client.cpp
index a41c020..6c5e631 100644
--- a/src/dawn/wire/client/Client.cpp
+++ b/src/dawn/wire/client/Client.cpp
@@ -61,7 +61,7 @@
         cmd.objectType = ObjectType::Device;
         cmd.objectId = object->GetWireId();
         SerializeCommand(cmd);
-        FreeObject(ObjectType::Device, object);
+        mObjectStores[ObjectType::Device].Free(object);
     }
 
     for (auto& objectList : mObjects) {
@@ -76,13 +76,13 @@
             cmd.objectType = objectType;
             cmd.objectId = object->GetWireId();
             SerializeCommand(cmd);
-            FreeObject(objectType, object);
+            mObjectStores[objectType].Free(object);
         }
     }
 }
 
 ReservedTexture Client::ReserveTexture(WGPUDevice device) {
-    Texture* texture = TextureAllocator().New(this);
+    Texture* texture = Make<Texture>();
 
     ReservedTexture result;
     result.texture = ToAPI(texture);
@@ -94,7 +94,7 @@
 }
 
 ReservedSwapChain Client::ReserveSwapChain(WGPUDevice device) {
-    SwapChain* swapChain = SwapChainAllocator().New(this);
+    SwapChain* swapChain = Make<SwapChain>();
 
     ReservedSwapChain result;
     result.swapchain = ToAPI(swapChain);
@@ -106,7 +106,7 @@
 }
 
 ReservedDevice Client::ReserveDevice() {
-    Device* device = DeviceAllocator().New(this);
+    Device* device = Make<Device>();
 
     ReservedDevice result;
     result.device = ToAPI(device);
@@ -116,7 +116,7 @@
 }
 
 ReservedInstance Client::ReserveInstance() {
-    Instance* instance = InstanceAllocator().New(this);
+    Instance* instance = Make<Instance>();
 
     ReservedInstance result;
     result.instance = ToAPI(instance);
@@ -126,19 +126,19 @@
 }
 
 void Client::ReclaimTextureReservation(const ReservedTexture& reservation) {
-    TextureAllocator().Free(FromAPI(reservation.texture));
+    Free(FromAPI(reservation.texture));
 }
 
 void Client::ReclaimSwapChainReservation(const ReservedSwapChain& reservation) {
-    SwapChainAllocator().Free(FromAPI(reservation.swapchain));
+    Free(FromAPI(reservation.swapchain));
 }
 
 void Client::ReclaimDeviceReservation(const ReservedDevice& reservation) {
-    DeviceAllocator().Free(FromAPI(reservation.device));
+    Free(FromAPI(reservation.device));
 }
 
 void Client::ReclaimInstanceReservation(const ReservedInstance& reservation) {
-    InstanceAllocator().Free(FromAPI(reservation.instance));
+    Free(FromAPI(reservation.instance));
 }
 
 void Client::Disconnect() {
@@ -165,4 +165,8 @@
     return mDisconnected;
 }
 
+void Client::Free(ObjectBase* obj, ObjectType type) {
+    mObjectStores[type].Free(obj);
+}
+
 }  // namespace dawn::wire::client
diff --git a/src/dawn/wire/client/Client.h b/src/dawn/wire/client/Client.h
index d045f0c..ad5769a 100644
--- a/src/dawn/wire/client/Client.h
+++ b/src/dawn/wire/client/Client.h
@@ -16,6 +16,7 @@
 #define SRC_DAWN_WIRE_CLIENT_CLIENT_H_
 
 #include <memory>
+#include <utility>
 
 #include "dawn/common/LinkedList.h"
 #include "dawn/common/NonCopyable.h"
@@ -26,6 +27,7 @@
 #include "dawn/wire/WireCmd_autogen.h"
 #include "dawn/wire/WireDeserializeAllocator.h"
 #include "dawn/wire/client/ClientBase_autogen.h"
+#include "dawn/wire/client/ObjectStore.h"
 
 namespace dawn::wire::client {
 
@@ -37,6 +39,32 @@
     Client(CommandSerializer* serializer, MemoryTransferService* memoryTransferService);
     ~Client() override;
 
+    // Make<T>(arg1, arg2, arg3) creates a new T, calling a constructor of the form:
+    //
+    //   T::T(ObjectBaseParams, arg1, arg2, arg3)
+    template <typename T, typename... Args>
+    T* Make(Args&&... args) {
+        constexpr ObjectType type = ObjectTypeToTypeEnum<T>::value;
+
+        ObjectBaseParams params = {this, mObjectStores[type].ReserveHandle()};
+        T* object = new T(params, std::forward<Args>(args)...);
+
+        mObjects[type].Append(object);
+        mObjectStores[type].Insert(std::unique_ptr<T>(object));
+        return object;
+    }
+
+    template <typename T>
+    void Free(T* obj) {
+        Free(obj, ObjectTypeToTypeEnum<T>::value);
+    }
+    void Free(ObjectBase* obj, ObjectType type);
+
+    template <typename T>
+    T* Get(ObjectId id) {
+        return static_cast<T*>(mObjectStores[ObjectTypeToTypeEnum<T>::value].Get(id));
+    }
+
     // ChunkedCommandHandler implementation
     const volatile char* HandleCommandsImpl(const volatile char* commands, size_t size) override;
 
@@ -67,21 +95,16 @@
     void Disconnect();
     bool IsDisconnected() const;
 
-    template <typename T>
-    void TrackObject(T* object) {
-        mObjects[ObjectTypeToTypeEnum<T>::value].Append(object);
-    }
-
   private:
     void DestroyAllObjects();
 
 #include "dawn/wire/client/ClientPrototypes_autogen.inc"
 
     ChunkedCommandSerializer mSerializer;
-    WireDeserializeAllocator mAllocator;
+    WireDeserializeAllocator mWireCommandAllocator;
+    PerObjectType<ObjectStore> mObjectStores;
     MemoryTransferService* mMemoryTransferService = nullptr;
     std::unique_ptr<MemoryTransferService> mOwnedMemoryTransferService = nullptr;
-
     PerObjectType<LinkedList<ObjectBase>> mObjects;
     bool mDisconnected = false;
 };
diff --git a/src/dawn/wire/client/Device.cpp b/src/dawn/wire/client/Device.cpp
index c09cb3b..d945694 100644
--- a/src/dawn/wire/client/Device.cpp
+++ b/src/dawn/wire/client/Device.cpp
@@ -20,7 +20,6 @@
 #include "dawn/common/Log.h"
 #include "dawn/wire/client/ApiObjects_autogen.h"
 #include "dawn/wire/client/Client.h"
-#include "dawn/wire/client/ObjectAllocator.h"
 
 namespace dawn::wire::client {
 
@@ -221,7 +220,7 @@
     if (mQueue == nullptr) {
         // Get the primary queue for this device.
         Client* client = GetClient();
-        mQueue = client->QueueAllocator().New(client);
+        mQueue = client->Make<Queue>();
 
         DeviceGetQueueCmd cmd;
         cmd.self = ToAPI(this);
@@ -243,7 +242,7 @@
                         "GPU device disconnected", userdata);
     }
 
-    ComputePipeline* pipeline = client->ComputePipelineAllocator().New(client);
+    ComputePipeline* pipeline = client->Make<ComputePipeline>();
 
     CreatePipelineAsyncRequest request = {};
     request.createComputePipelineAsyncCallback = callback;
@@ -270,20 +269,17 @@
     }
 
     Client* client = GetClient();
-    auto* pipelineAllocation =
-        client->ComputePipelineAllocator().GetObject(request.pipelineObjectID);
+    ComputePipeline* pipeline = client->Get<ComputePipeline>(request.pipelineObjectID);
 
     // If the return status is a failure we should give a null pipeline to the callback and
     // free the allocation.
     if (status != WGPUCreatePipelineAsyncStatus_Success) {
-        client->ComputePipelineAllocator().Free(pipelineAllocation);
+        client->Free(pipeline);
         request.createComputePipelineAsyncCallback(status, nullptr, message, request.userdata);
         return true;
     }
 
-    WGPUComputePipeline pipeline = reinterpret_cast<WGPUComputePipeline>(pipelineAllocation);
-    request.createComputePipelineAsyncCallback(status, pipeline, message, request.userdata);
-
+    request.createComputePipelineAsyncCallback(status, ToAPI(pipeline), message, request.userdata);
     return true;
 }
 
@@ -296,7 +292,7 @@
                         "GPU device disconnected", userdata);
     }
 
-    RenderPipeline* pipeline = client->RenderPipelineAllocator().New(client);
+    RenderPipeline* pipeline = client->Make<RenderPipeline>();
 
     CreatePipelineAsyncRequest request = {};
     request.createRenderPipelineAsyncCallback = callback;
@@ -323,20 +319,17 @@
     }
 
     Client* client = GetClient();
-    auto* pipelineAllocation =
-        client->RenderPipelineAllocator().GetObject(request.pipelineObjectID);
+    RenderPipeline* pipeline = client->Get<RenderPipeline>(request.pipelineObjectID);
 
     // If the return status is a failure we should give a null pipeline to the callback and
     // free the allocation.
     if (status != WGPUCreatePipelineAsyncStatus_Success) {
-        client->RenderPipelineAllocator().Free(pipelineAllocation);
+        client->Free(pipeline);
         request.createRenderPipelineAsyncCallback(status, nullptr, message, request.userdata);
         return true;
     }
 
-    WGPURenderPipeline pipeline = reinterpret_cast<WGPURenderPipeline>(pipelineAllocation);
-    request.createRenderPipelineAsyncCallback(status, pipeline, message, request.userdata);
-
+    request.createRenderPipelineAsyncCallback(status, ToAPI(pipeline), message, request.userdata);
     return true;
 }
 
diff --git a/src/dawn/wire/client/Instance.cpp b/src/dawn/wire/client/Instance.cpp
index 9812f2e..efd9b74 100644
--- a/src/dawn/wire/client/Instance.cpp
+++ b/src/dawn/wire/client/Instance.cpp
@@ -41,7 +41,7 @@
         return;
     }
 
-    Adapter* adapter = client->AdapterAllocator().New(client);
+    Adapter* adapter = client->Make<Adapter>();
     uint64_t serial = mRequestAdapterRequests.Add({callback, adapter->GetWireId(), userdata});
 
     InstanceRequestAdapterCmd cmd;
@@ -82,12 +82,12 @@
     }
 
     Client* client = GetClient();
-    Adapter* adapter = client->AdapterAllocator().GetObject(request.adapterObjectId);
+    Adapter* adapter = client->Get<Adapter>(request.adapterObjectId);
 
     // If the return status is a failure we should give a null adapter to the callback and
     // free the allocation.
     if (status != WGPURequestAdapterStatus_Success) {
-        client->AdapterAllocator().Free(adapter);
+        client->Free(adapter);
         request.callback(status, nullptr, message, request.userdata);
         return true;
     }
diff --git a/src/dawn/wire/client/ObjectAllocator.h b/src/dawn/wire/client/ObjectAllocator.h
deleted file mode 100644
index f369a70..0000000
--- a/src/dawn/wire/client/ObjectAllocator.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2019 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.
-
-#ifndef SRC_DAWN_WIRE_CLIENT_OBJECTALLOCATOR_H_
-#define SRC_DAWN_WIRE_CLIENT_OBJECTALLOCATOR_H_
-
-#include <limits>
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "dawn/common/Assert.h"
-#include "dawn/common/Compiler.h"
-#include "dawn/wire/WireCmd_autogen.h"
-#include "dawn/wire/client/ObjectBase.h"
-
-namespace dawn::wire::client {
-
-template <typename T>
-class ObjectAllocator {
-  public:
-    ObjectAllocator() {
-        // ID 0 is nullptr
-        mObjects.emplace_back(nullptr);
-    }
-
-    template <typename Client>
-    T* New(Client* client) {
-        ObjectHandle handle = GetFreeHandle();
-        ObjectBaseParams params = {client, handle};
-        auto object = std::make_unique<T>(params);
-        client->TrackObject(object.get());
-
-        if (handle.id >= mObjects.size()) {
-            ASSERT(handle.id == mObjects.size());
-            mObjects.emplace_back(std::move(object));
-        } else {
-            // The generation should never overflow. We don't recycle ObjectIds that would
-            // overflow their next generation.
-            ASSERT(handle.generation != 0);
-            ASSERT(mObjects[handle.id] == nullptr);
-            mObjects[handle.id] = std::move(object);
-        }
-
-        return mObjects[handle.id].get();
-    }
-    void Free(T* obj) {
-        ASSERT(obj->IsInList());
-        // The wire reuses ID for objects to keep them in a packed array starting from 0.
-        // To avoid issues with asynchronous server->client communication referring to an ID that's
-        // already reused, each handle also has a generation that's increment by one on each reuse.
-        // Avoid overflows by only reusing the ID if the increment of the generation won't overflow.
-        ObjectHandle currentHandle = obj->GetWireHandle();
-        if (DAWN_LIKELY(currentHandle.generation != std::numeric_limits<ObjectGeneration>::max())) {
-            mFreeHandles.push_back({currentHandle.id, currentHandle.generation + 1});
-        }
-        mObjects[currentHandle.id] = nullptr;
-    }
-
-    T* GetObject(uint32_t id) {
-        if (id >= mObjects.size()) {
-            return nullptr;
-        }
-        return mObjects[id].get();
-    }
-
-  private:
-    ObjectHandle GetFreeHandle() {
-        if (mFreeHandles.empty()) {
-            return {mCurrentId++, 0};
-        }
-        ObjectHandle handle = mFreeHandles.back();
-        mFreeHandles.pop_back();
-        return handle;
-    }
-
-    // 0 is an ID reserved to represent nullptr
-    uint32_t mCurrentId = 1;
-    std::vector<ObjectHandle> mFreeHandles;
-    std::vector<std::unique_ptr<T>> mObjects;
-};
-}  // namespace dawn::wire::client
-
-#endif  // SRC_DAWN_WIRE_CLIENT_OBJECTALLOCATOR_H_
diff --git a/src/dawn/wire/client/ObjectStore.cpp b/src/dawn/wire/client/ObjectStore.cpp
new file mode 100644
index 0000000..299a678
--- /dev/null
+++ b/src/dawn/wire/client/ObjectStore.cpp
@@ -0,0 +1,72 @@
+// Copyright 2022 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 "dawn/wire/client/ObjectStore.h"
+
+#include <limits>
+#include <utility>
+
+namespace dawn::wire::client {
+
+ObjectStore::ObjectStore() {
+    // ID 0 is nullptr
+    mObjects.emplace_back(nullptr);
+    mCurrentId = 1;
+}
+
+ObjectHandle ObjectStore::ReserveHandle() {
+    if (mFreeHandles.empty()) {
+        return {mCurrentId++, 0};
+    }
+    ObjectHandle handle = mFreeHandles.back();
+    mFreeHandles.pop_back();
+    return handle;
+}
+
+void ObjectStore::Insert(std::unique_ptr<ObjectBase> obj) {
+    ObjectId id = obj->GetWireId();
+
+    if (id >= mObjects.size()) {
+        ASSERT(id == mObjects.size());
+        mObjects.emplace_back(std::move(obj));
+    } else {
+        // The generation should never overflow. We don't recycle ObjectIds that would
+        // overflow their next generation.
+        ASSERT(obj->GetWireGeneration() != 0);
+        ASSERT(mObjects[id] == nullptr);
+        mObjects[id] = std::move(obj);
+    }
+}
+
+void ObjectStore::Free(ObjectBase* obj) {
+    ASSERT(obj->IsInList());
+    // The wire reuses ID for objects to keep them in a packed array starting from 0.
+    // To avoid issues with asynchronous server->client communication referring to an ID that's
+    // already reused, each handle also has a generation that's increment by one on each reuse.
+    // Avoid overflows by only reusing the ID if the increment of the generation won't overflow.
+    const ObjectHandle& currentHandle = obj->GetWireHandle();
+    if (DAWN_LIKELY(currentHandle.generation != std::numeric_limits<ObjectGeneration>::max())) {
+        mFreeHandles.push_back({currentHandle.id, currentHandle.generation + 1});
+    }
+    mObjects[currentHandle.id] = nullptr;
+}
+
+ObjectBase* ObjectStore::Get(ObjectId id) const {
+    if (id >= mObjects.size()) {
+        return nullptr;
+    }
+    return mObjects[id].get();
+}
+
+}  // namespace dawn::wire::client
diff --git a/src/dawn/wire/client/ObjectStore.h b/src/dawn/wire/client/ObjectStore.h
new file mode 100644
index 0000000..5d9b741
--- /dev/null
+++ b/src/dawn/wire/client/ObjectStore.h
@@ -0,0 +1,51 @@
+// Copyright 2019 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.
+
+#ifndef SRC_DAWN_WIRE_CLIENT_OBJECTSTORE_H_
+#define SRC_DAWN_WIRE_CLIENT_OBJECTSTORE_H_
+
+#include <memory>
+#include <vector>
+
+#include "dawn/wire/client/ObjectBase.h"
+
+namespace dawn::wire::client {
+
+class Client;
+
+// A helper class used in Client, ObjectStore owns the association of some ObjectBase and
+// ObjectHandles. The lifetime of the ObjectBase is then owned by the ObjectStore, destruction
+// happening when Free is called.
+//
+// Since the wire has one "ID" namespace per type of object, each ObjectStore should contain a
+// single type of objects. However no templates are used because Client wraps ObjectStore and is
+// type-generic, so ObjectStore is type-erased to only work on ObjectBase.
+class ObjectStore {
+  public:
+    ObjectStore();
+
+    ObjectHandle ReserveHandle();
+    void Insert(std::unique_ptr<ObjectBase> obj);
+    void Free(ObjectBase* obj);
+    ObjectBase* Get(ObjectId id) const;
+
+  private:
+    uint32_t mCurrentId;
+    std::vector<ObjectHandle> mFreeHandles;
+    std::vector<std::unique_ptr<ObjectBase>> mObjects;
+};
+
+}  // namespace dawn::wire::client
+
+#endif  // SRC_DAWN_WIRE_CLIENT_OBJECTSTORE_H_
diff --git a/src/dawn/wire/client/QuerySet.cpp b/src/dawn/wire/client/QuerySet.cpp
index 903c65d..4a22a0c 100644
--- a/src/dawn/wire/client/QuerySet.cpp
+++ b/src/dawn/wire/client/QuerySet.cpp
@@ -22,7 +22,7 @@
 // static
 WGPUQuerySet QuerySet::Create(Device* device, const WGPUQuerySetDescriptor* descriptor) {
     Client* wireClient = device->GetClient();
-    QuerySet* querySet = wireClient->QuerySetAllocator().New(wireClient);
+    QuerySet* querySet = wireClient->Make<QuerySet>();
 
     // Copy over descriptor data for reflection.
     querySet->mType = descriptor->type;
diff --git a/src/dawn/wire/client/Texture.cpp b/src/dawn/wire/client/Texture.cpp
index 99ea97f..1fffde2 100644
--- a/src/dawn/wire/client/Texture.cpp
+++ b/src/dawn/wire/client/Texture.cpp
@@ -22,7 +22,7 @@
 // static
 WGPUTexture Texture::Create(Device* device, const WGPUTextureDescriptor* descriptor) {
     Client* wireClient = device->GetClient();
-    Texture* texture = wireClient->TextureAllocator().New(wireClient);
+    Texture* texture = wireClient->Make<Texture>();
 
     // Copy over descriptor data for reflection.
     texture->mSize = descriptor->size;