blob: e186a4252c41f97dfd4efffe073ec337061de6b1 [file] [log] [blame]
//* Copyright 2017 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/TypeTraits_autogen.h"
#include "dawn_wire/Wire.h"
#include "dawn_wire/WireCmd.h"
#include "common/Assert.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <map>
#include <memory>
#include <vector>
namespace dawn_wire {
namespace server {
class Server;
struct MapUserdata {
Server* server;
uint32_t bufferId;
uint32_t bufferSerial;
uint32_t requestSerial;
uint32_t size;
bool isWrite;
};
struct FenceCompletionUserdata {
Server* server;
uint32_t fenceId;
uint32_t fenceSerial;
uint64_t value;
};
template <typename T>
struct ObjectDataBase {
//* The backend-provided handle and serial to this object.
T handle;
uint32_t serial = 0;
//* Used by the error-propagation mechanism to know if this object is an error.
//* TODO(cwallez@chromium.org): this is doubling the memory usage of
//* std::vector<ObjectDataBase> consider making it a special marker value in handle instead.
bool valid;
//* Whether this object has been allocated, used by the KnownObjects queries
//* TODO(cwallez@chromium.org): make this an internal bit vector in KnownObjects.
bool allocated;
};
//* Stores what the backend knows about the type.
template<typename T, bool IsBuilder = IsBuilderType<T>::value>
struct ObjectData : public ObjectDataBase<T> {
};
template <typename T>
struct ObjectData<T, true> : public ObjectDataBase<T> {
uint32_t builtObjectId = 0;
uint32_t builtObjectSerial = 0;
};
template <>
struct ObjectData<dawnBuffer, false> : public ObjectDataBase<dawnBuffer> {
void* mappedData = nullptr;
size_t mappedDataSize = 0;
};
//* Keeps track of the mapping between client IDs and backend objects.
template<typename T>
class KnownObjects {
public:
using Data = ObjectData<T>;
KnownObjects() {
//* Pre-allocate ID 0 to refer to the null handle.
Data nullObject;
nullObject.handle = nullptr;
nullObject.valid = true;
nullObject.allocated = true;
mKnown.push_back(nullObject);
}
//* Get a backend objects for a given client ID.
//* Returns nullptr if the ID hasn't previously been allocated.
const Data* Get(uint32_t id) const {
if (id >= mKnown.size()) {
return nullptr;
}
const Data* data = &mKnown[id];
if (!data->allocated) {
return nullptr;
}
return data;
}
Data* Get(uint32_t id) {
if (id >= mKnown.size()) {
return nullptr;
}
Data* data = &mKnown[id];
if (!data->allocated) {
return nullptr;
}
return data;
}
//* Allocates the data for a given ID and returns it.
//* Returns nullptr if the ID is already allocated, or too far ahead.
//* Invalidates all the Data*
Data* Allocate(uint32_t id) {
if (id > mKnown.size()) {
return nullptr;
}
Data data;
data.allocated = true;
data.valid = false;
data.handle = nullptr;
if (id >= mKnown.size()) {
mKnown.push_back(data);
return &mKnown.back();
}
if (mKnown[id].allocated) {
return nullptr;
}
mKnown[id] = data;
return &mKnown[id];
}
//* Marks an ID as deallocated
void Free(uint32_t id) {
ASSERT(id < mKnown.size());
mKnown[id].allocated = false;
}
std::vector<T> AcquireAllHandles() {
std::vector<T> objects;
for (Data& data : mKnown) {
if (data.allocated && data.handle != nullptr) {
objects.push_back(data.handle);
data.valid = false;
data.allocated = false;
data.handle = nullptr;
}
}
return objects;
}
private:
std::vector<Data> mKnown;
};
// ObjectIds are lost in deserialization. Store the ids of deserialized
// objects here so they can be used in command handlers. This is useful
// for creating ReturnWireCmds which contain client ids
template <typename T>
class ObjectIdLookupTable {
public:
void Store(T key, ObjectId id) {
mTable[key] = id;
}
// Return the cached ObjectId, or 0 (null handle)
ObjectId Get(T key) const {
const auto it = mTable.find(key);
if (it != mTable.end()) {
return it->second;
}
return 0;
}
void Remove(T key) {
auto it = mTable.find(key);
if (it != mTable.end()) {
mTable.erase(it);
}
}
private:
std::map<T, ObjectId> mTable;
};
void ForwardDeviceErrorToServer(const char* message, dawnCallbackUserdata userdata);
{% for type in by_category["object"] if type.is_builder%}
void Forward{{type.name.CamelCase()}}ToClient(dawnBuilderErrorStatus status, const char* message, dawnCallbackUserdata userdata1, dawnCallbackUserdata userdata2);
{% endfor %}
void ForwardBufferMapReadAsync(dawnBufferMapAsyncStatus status, const void* ptr, dawnCallbackUserdata userdata);
void ForwardBufferMapWriteAsync(dawnBufferMapAsyncStatus status, void* ptr, dawnCallbackUserdata userdata);
void ForwardFenceCompletedValue(dawnFenceCompletionStatus status,
dawnCallbackUserdata userdata);
// A really really simple implementation of the DeserializeAllocator. It's main feature
// is that it has some inline storage so as to avoid allocations for the majority of
// commands.
class ServerAllocator : public DeserializeAllocator {
public:
ServerAllocator() {
Reset();
}
~ServerAllocator() {
Reset();
}
void* GetSpace(size_t size) override {
// Return space in the current buffer if possible first.
if (mRemainingSize >= size) {
char* buffer = mCurrentBuffer;
mCurrentBuffer += size;
mRemainingSize -= size;
return buffer;
}
// Otherwise allocate a new buffer and try again.
size_t allocationSize = std::max(size, size_t(2048));
char* allocation = static_cast<char*>(malloc(allocationSize));
if (allocation == nullptr) {
return nullptr;
}
mAllocations.push_back(allocation);
mCurrentBuffer = allocation;
mRemainingSize = allocationSize;
return GetSpace(size);
}
void Reset() {
for (auto allocation : mAllocations) {
free(allocation);
}
mAllocations.clear();
// The initial buffer is the inline buffer so that some allocations can be skipped
mCurrentBuffer = mStaticBuffer;
mRemainingSize = sizeof(mStaticBuffer);
}
private:
size_t mRemainingSize = 0;
char* mCurrentBuffer = nullptr;
char mStaticBuffer[2048];
std::vector<char*> mAllocations;
};
class Server : public CommandHandler, public ObjectIdResolver {
public:
Server(dawnDevice device, const dawnProcTable& procs, CommandSerializer* serializer)
: mProcs(procs), mSerializer(serializer) {
//* The client-server knowledge is bootstrapped with device 1.
auto* deviceData = mKnownDevice.Allocate(1);
deviceData->handle = device;
deviceData->valid = true;
auto userdata = static_cast<dawnCallbackUserdata>(reinterpret_cast<intptr_t>(this));
procs.deviceSetErrorCallback(device, ForwardDeviceErrorToServer, userdata);
}
~Server() override {
//* Free all objects when the server is destroyed
{% for type in by_category["object"] if type.name.canonical_case() != "device" %}
{
std::vector<{{as_cType(type.name)}}> handles = mKnown{{type.name.CamelCase()}}.AcquireAllHandles();
for ({{as_cType(type.name)}} handle : handles) {
mProcs.{{as_varName(type.name, Name("release"))}}(handle);
}
}
{% endfor %}
}
void OnDeviceError(const char* message) {
ReturnDeviceErrorCallbackCmd cmd;
cmd.messageStrlen = std::strlen(message);
auto allocCmd = static_cast<ReturnDeviceErrorCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
*allocCmd = cmd;
char* messageAlloc = static_cast<char*>(GetCmdSpace(cmd.messageStrlen + 1));
strcpy(messageAlloc, message);
}
{% for type in by_category["object"] if type.is_builder%}
{% set Type = type.name.CamelCase() %}
void On{{Type}}Error(dawnBuilderErrorStatus status, const char* message, uint32_t id, uint32_t serial) {
auto* builder = mKnown{{Type}}.Get(id);
if (builder == nullptr || builder->serial != serial) {
return;
}
if (status != DAWN_BUILDER_ERROR_STATUS_SUCCESS) {
builder->valid = false;
}
if (status != DAWN_BUILDER_ERROR_STATUS_UNKNOWN) {
//* Unknown is the only status that can be returned without a call to GetResult
//* so we are guaranteed to have created an object.
ASSERT(builder->builtObjectId != 0);
Return{{Type}}ErrorCallbackCmd cmd;
cmd.builtObjectId = builder->builtObjectId;
cmd.builtObjectSerial = builder->builtObjectSerial;
cmd.status = status;
cmd.messageStrlen = std::strlen(message);
auto allocCmd = static_cast<Return{{Type}}ErrorCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
*allocCmd = cmd;
char* messageAlloc = static_cast<char*>(GetCmdSpace(strlen(message) + 1));
strcpy(messageAlloc, message);
}
}
{% endfor %}
void OnMapReadAsyncCallback(dawnBufferMapAsyncStatus status, const void* ptr, MapUserdata* userdata) {
std::unique_ptr<MapUserdata> data(userdata);
// Skip sending the callback if the buffer has already been destroyed.
auto* bufferData = mKnownBuffer.Get(data->bufferId);
if (bufferData == nullptr || bufferData->serial != data->bufferSerial) {
return;
}
ReturnBufferMapReadAsyncCallbackCmd cmd;
cmd.bufferId = data->bufferId;
cmd.bufferSerial = data->bufferSerial;
cmd.requestSerial = data->requestSerial;
cmd.status = status;
cmd.dataLength = 0;
auto allocCmd = static_cast<ReturnBufferMapReadAsyncCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
*allocCmd = cmd;
if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
allocCmd->dataLength = data->size;
void* dataAlloc = GetCmdSpace(data->size);
memcpy(dataAlloc, ptr, data->size);
}
}
void OnMapWriteAsyncCallback(dawnBufferMapAsyncStatus status, void* ptr, MapUserdata* userdata) {
std::unique_ptr<MapUserdata> data(userdata);
// Skip sending the callback if the buffer has already been destroyed.
auto* bufferData = mKnownBuffer.Get(data->bufferId);
if (bufferData == nullptr || bufferData->serial != data->bufferSerial) {
return;
}
ReturnBufferMapWriteAsyncCallbackCmd cmd;
cmd.bufferId = data->bufferId;
cmd.bufferSerial = data->bufferSerial;
cmd.requestSerial = data->requestSerial;
cmd.status = status;
auto allocCmd = static_cast<ReturnBufferMapWriteAsyncCallbackCmd*>(GetCmdSpace(sizeof(cmd)));
*allocCmd = cmd;
if (status == DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS) {
bufferData->mappedData = ptr;
bufferData->mappedDataSize = data->size;
}
}
void OnFenceCompletedValueUpdated(FenceCompletionUserdata* userdata) {
std::unique_ptr<FenceCompletionUserdata> data(userdata);
ReturnFenceUpdateCompletedValueCmd cmd;
cmd.fenceId = data->fenceId;
cmd.fenceSerial = data->fenceSerial;
cmd.value = data->value;
auto allocCmd = static_cast<ReturnFenceUpdateCompletedValueCmd*>(GetCmdSpace(sizeof(cmd)));
*allocCmd = cmd;
}
{% set client_side_commands = ["FenceGetCompletedValue"] %}
const char* HandleCommands(const char* commands, size_t size) override {
mProcs.deviceTick(mKnownDevice.Get(1)->handle);
while (size >= sizeof(WireCmd)) {
WireCmd cmdId = *reinterpret_cast<const WireCmd*>(commands);
bool success = false;
switch (cmdId) {
{% for type in by_category["object"] %}
{% for method in type.methods %}
{% set Suffix = as_MethodSuffix(type.name, method.name) %}
{% if Suffix not in client_side_commands %}
case WireCmd::{{Suffix}}:
success = Handle{{Suffix}}(&commands, &size);
break;
{% endif %}
{% endfor %}
{% endfor %}
case WireCmd::BufferMapAsync:
success = HandleBufferMapAsync(&commands, &size);
break;
case WireCmd::BufferUpdateMappedDataCmd:
success = HandleBufferUpdateMappedData(&commands, &size);
break;
case WireCmd::DestroyObject:
success = HandleDestroyObject(&commands, &size);
break;
default:
success = false;
}
if (!success) {
return nullptr;
}
mAllocator.Reset();
}
if (size != 0) {
return nullptr;
}
return commands;
}
private:
dawnProcTable mProcs;
CommandSerializer* mSerializer = nullptr;
ServerAllocator mAllocator;
void* GetCmdSpace(size_t size) {
return mSerializer->GetCmdSpace(size);
}
// Implementation of the ObjectIdResolver interface
{% for type in by_category["object"] %}
DeserializeResult GetFromId(ObjectId id, {{as_cType(type.name)}}* out) const final {
auto data = mKnown{{type.name.CamelCase()}}.Get(id);
if (data == nullptr) {
return DeserializeResult::FatalError;
}
*out = data->handle;
if (data->valid) {
return DeserializeResult::Success;
} else {
return DeserializeResult::ErrorObject;
}
}
DeserializeResult GetOptionalFromId(ObjectId id, {{as_cType(type.name)}}* out) const final {
if (id == 0) {
*out = nullptr;
return DeserializeResult::Success;
}
return GetFromId(id, out);
}
{% endfor %}
//* The list of known IDs for each object type.
{% for type in by_category["object"] %}
KnownObjects<{{as_cType(type.name)}}> mKnown{{type.name.CamelCase()}};
{% endfor %}
{% set reverse_lookup_object_types = ["Fence"] %}
{% for type in by_category["object"] if type.name.CamelCase() in reverse_lookup_object_types %}
ObjectIdLookupTable<{{as_cType(type.name)}}> m{{type.name.CamelCase()}}IdTable;
{% endfor %}
//* Helper function for the getting of the command data in command handlers.
//* Checks there is enough data left, updates the buffer / size and returns
//* the command (or nullptr for an error).
template <typename T>
static const T* GetData(const char** buffer, size_t* size, size_t count) {
// TODO(cwallez@chromium.org): Check for overflow
size_t totalSize = count * sizeof(T);
if (*size < totalSize) {
return nullptr;
}
const T* data = reinterpret_cast<const T*>(*buffer);
*buffer += totalSize;
*size -= totalSize;
return data;
}
template <typename T>
static const T* GetCommand(const char** commands, size_t* size) {
return GetData<T>(commands, size, 1);
}
{% set custom_pre_handler_commands = ["BufferUnmap"] %}
bool PreHandleBufferUnmap(const BufferUnmapCmd& cmd) {
auto* selfData = mKnownBuffer.Get(cmd.selfId);
ASSERT(selfData != nullptr);
selfData->mappedData = nullptr;
return true;
}
{% set custom_post_handler_commands = ["QueueSignal"] %}
bool PostHandleQueueSignal(const QueueSignalCmd& cmd) {
if (cmd.fence == nullptr) {
return false;
}
ObjectId fenceId = mFenceIdTable.Get(cmd.fence);
ASSERT(fenceId != 0);
auto* fence = mKnownFence.Get(fenceId);
ASSERT(fence != nullptr);
auto* data = new FenceCompletionUserdata;
data->server = this;
data->fenceId = fenceId;
data->fenceSerial = fence->serial;
data->value = cmd.signalValue;
auto userdata = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data));
mProcs.fenceOnCompletion(cmd.fence, cmd.signalValue, ForwardFenceCompletedValue, userdata);
return true;
}
//* Implementation of the command handlers
{% for type in by_category["object"] %}
{% for method in type.methods %}
{% set Suffix = as_MethodSuffix(type.name, method.name) %}
{% if Suffix not in client_side_commands %}
//* The generic command handlers
bool Handle{{Suffix}}(const char** commands, size_t* size) {
{{Suffix}}Cmd cmd;
DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator, *this);
if (deserializeResult == DeserializeResult::FatalError) {
return false;
}
{% if Suffix in custom_pre_handler_commands %}
if (!PreHandle{{Suffix}}(cmd)) {
return false;
}
{% endif %}
//* Unpack 'self'
auto* selfData = mKnown{{type.name.CamelCase()}}.Get(cmd.selfId);
ASSERT(selfData != nullptr);
//* In all cases allocate the object data as it will be refered-to by the client.
{% set return_type = method.return_type %}
{% set returns = return_type.name.canonical_case() != "void" %}
{% if returns %}
{% set Type = method.return_type.name.CamelCase() %}
auto* resultData = mKnown{{Type}}.Allocate(cmd.resultId);
if (resultData == nullptr) {
return false;
}
resultData->serial = cmd.resultSerial;
{% if type.is_builder %}
selfData->builtObjectId = cmd.resultId;
selfData->builtObjectSerial = cmd.resultSerial;
{% endif %}
{% endif %}
//* After the data is allocated, apply the argument error propagation mechanism
if (deserializeResult == DeserializeResult::ErrorObject) {
{% if type.is_builder %}
selfData->valid = false;
//* If we are in GetResult, fake an error callback
{% if returns %}
On{{type.name.CamelCase()}}Error(DAWN_BUILDER_ERROR_STATUS_ERROR, "Maybe monad", cmd.selfId, selfData->serial);
{% endif %}
{% endif %}
return true;
}
{% if returns %}
auto result ={{" "}}
{%- endif %}
mProcs.{{as_varName(type.name, method.name)}}(cmd.self
{%- for arg in method.arguments -%}
, cmd.{{as_varName(arg.name)}}
{%- endfor -%}
);
{% if Suffix in custom_post_handler_commands %}
if (!PostHandle{{Suffix}}(cmd)) {
return false;
}
{% endif %}
{% if returns %}
resultData->handle = result;
resultData->valid = result != nullptr;
{% if return_type.name.CamelCase() in reverse_lookup_object_types %}
//* For created objects, store a mapping from them back to their client IDs
if (result) {
m{{return_type.name.CamelCase()}}IdTable.Store(result, cmd.resultId);
}
{% endif %}
//* builders remember the ID of the object they built so that they can send it
//* in the callback to the client.
{% if return_type.is_builder %}
if (result != nullptr) {
uint64_t userdata1 = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this));
uint64_t userdata2 = (uint64_t(resultData->serial) << uint64_t(32)) + cmd.resultId;
mProcs.{{as_varName(return_type.name, Name("set error callback"))}}(result, Forward{{return_type.name.CamelCase()}}ToClient, userdata1, userdata2);
}
{% endif %}
{% endif %}
return true;
}
{% endif %}
{% endfor %}
{% endfor %}
bool HandleBufferMapAsync(const char** commands, size_t* size) {
//* These requests are just forwarded to the buffer, with userdata containing what the client
//* will require in the return command.
const auto* cmd = GetCommand<BufferMapAsyncCmd>(commands, size);
if (cmd == nullptr) {
return false;
}
ObjectId bufferId = cmd->bufferId;
uint32_t requestSerial = cmd->requestSerial;
uint32_t requestSize = cmd->size;
uint32_t requestStart = cmd->start;
bool isWrite = cmd->isWrite;
//* The null object isn't valid as `self`
if (bufferId == 0) {
return false;
}
auto* buffer = mKnownBuffer.Get(bufferId);
if (buffer == nullptr) {
return false;
}
auto* data = new MapUserdata;
data->server = this;
data->bufferId = bufferId;
data->bufferSerial = buffer->serial;
data->requestSerial = requestSerial;
data->size = requestSize;
data->isWrite = isWrite;
auto userdata = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data));
if (!buffer->valid) {
//* Fake the buffer returning a failure, data will be freed in this call.
if (isWrite) {
ForwardBufferMapWriteAsync(DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR, nullptr, userdata);
} else {
ForwardBufferMapReadAsync(DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR, nullptr, userdata);
}
return true;
}
if (isWrite) {
mProcs.bufferMapWriteAsync(buffer->handle, requestStart, requestSize, ForwardBufferMapWriteAsync, userdata);
} else {
mProcs.bufferMapReadAsync(buffer->handle, requestStart, requestSize, ForwardBufferMapReadAsync, userdata);
}
return true;
}
bool HandleBufferUpdateMappedData(const char** commands, size_t* size) {
const auto* cmd = GetCommand<BufferUpdateMappedDataCmd>(commands, size);
if (cmd == nullptr) {
return false;
}
ObjectId bufferId = cmd->bufferId;
size_t dataLength = cmd->dataLength;
//* The null object isn't valid as `self`
if (bufferId == 0) {
return false;
}
auto* buffer = mKnownBuffer.Get(bufferId);
if (buffer == nullptr || !buffer->valid || buffer->mappedData == nullptr ||
buffer->mappedDataSize != dataLength) {
return false;
}
const char* data = GetData<char>(commands, size, dataLength);
if (data == nullptr) {
return false;
}
memcpy(buffer->mappedData, data, dataLength);
return true;
}
bool HandleDestroyObject(const char** commands, size_t* size) {
const auto* cmd = GetCommand<DestroyObjectCmd>(commands, size);
if (cmd == nullptr) {
return false;
}
ObjectId objectId = cmd->objectId;
//* ID 0 are reserved for nullptr and cannot be destroyed.
if (objectId == 0) {
return false;
}
switch (cmd->objectType) {
{% for type in by_category["object"] %}
{% set ObjectType = type.name.CamelCase() %}
case ObjectType::{{ObjectType}}: {
{% if ObjectType == "Device" %}
//* Freeing the device has to be done out of band.
return false;
{% else %}
auto* data = mKnown{{type.name.CamelCase()}}.Get(objectId);
if (data == nullptr) {
return false;
}
{% if type.name.CamelCase() in reverse_lookup_object_types %}
m{{type.name.CamelCase()}}IdTable.Remove(data->handle);
{% endif %}
if (data->handle != nullptr) {
mProcs.{{as_varName(type.name, Name("release"))}}(data->handle);
}
mKnown{{type.name.CamelCase()}}.Free(objectId);
return true;
{% endif %}
}
{% endfor %}
default:
UNREACHABLE();
}
}
};
void ForwardDeviceErrorToServer(const char* message, dawnCallbackUserdata userdata) {
auto server = reinterpret_cast<Server*>(static_cast<intptr_t>(userdata));
server->OnDeviceError(message);
}
{% for type in by_category["object"] if type.is_builder%}
void Forward{{type.name.CamelCase()}}ToClient(dawnBuilderErrorStatus status, const char* message, dawnCallbackUserdata userdata1, dawnCallbackUserdata userdata2) {
auto server = reinterpret_cast<Server*>(static_cast<uintptr_t>(userdata1));
uint32_t id = userdata2 & 0xFFFFFFFFu;
uint32_t serial = userdata2 >> uint64_t(32);
server->On{{type.name.CamelCase()}}Error(status, message, id, serial);
}
{% endfor %}
void ForwardBufferMapReadAsync(dawnBufferMapAsyncStatus status, const void* ptr, dawnCallbackUserdata userdata) {
auto data = reinterpret_cast<MapUserdata*>(static_cast<uintptr_t>(userdata));
data->server->OnMapReadAsyncCallback(status, ptr, data);
}
void ForwardBufferMapWriteAsync(dawnBufferMapAsyncStatus status, void* ptr, dawnCallbackUserdata userdata) {
auto data = reinterpret_cast<MapUserdata*>(static_cast<uintptr_t>(userdata));
data->server->OnMapWriteAsyncCallback(status, ptr, data);
}
void ForwardFenceCompletedValue(dawnFenceCompletionStatus status, dawnCallbackUserdata userdata) {
auto data = reinterpret_cast<FenceCompletionUserdata*>(static_cast<uintptr_t>(userdata));
if (status == DAWN_FENCE_COMPLETION_STATUS_SUCCESS) {
data->server->OnFenceCompletedValueUpdated(data);
}
}
}
CommandHandler* NewServerCommandHandler(dawnDevice device, const dawnProcTable& procs, CommandSerializer* serializer) {
return new server::Server(device, procs, serializer);
}
} // namespace dawn_wire