413 lines
14 KiB
Cheetah
413 lines
14 KiB
Cheetah
{{/*
|
|
Copyright 2021 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.
|
|
*/}}
|
|
|
|
{{- /*
|
|
--------------------------------------------------------------------------------
|
|
Template file for use with tools/src/cmd/idlgen/main.go to generate the
|
|
WebGPU.cpp source file.
|
|
|
|
See:
|
|
* https://github.com/ben-clayton/webidlparser/blob/main/ast/ast.go for the AST
|
|
types used by this template
|
|
* tools/src/cmd/idlgen/main.go for additional structures and functions
|
|
used by this template
|
|
* https://golang.org/pkg/text/template/ for documentation on the template syntax
|
|
--------------------------------------------------------------------------------
|
|
*/ -}}
|
|
|
|
{{- Include "WebGPUCommon.tmpl" -}}
|
|
|
|
#include "src/dawn/node/interop/WebGPU.h"
|
|
|
|
#include <unordered_map>
|
|
|
|
#include "src/dawn/node/utils/Debug.h"
|
|
|
|
namespace wgpu {
|
|
namespace interop {
|
|
|
|
namespace {
|
|
|
|
{{template "Wrappers" $}}
|
|
|
|
} // namespace
|
|
|
|
{{ range $ := .Declarations}}
|
|
{{- if IsDictionary $}}{{template "Dictionary" $}}
|
|
{{- else if IsInterface $}}{{template "Interface" $}}
|
|
{{- else if IsEnum $}}{{template "Enum" $}}
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
|
|
void Initialize(Napi::Env env) {
|
|
auto* wrapper = Wrappers::Init(env);
|
|
auto global = env.Global();
|
|
{{- range $ := .Declarations}}
|
|
{{- if IsInterfaceOrNamespace $}}
|
|
{{- if not (HasAnnotation $ "LegacyNoInterfaceObject")}}
|
|
global.Set(Napi::String::New(env, "{{$.Name}}"), wrapper->{{$.Name}}_ctor.Value());
|
|
{{- end}}
|
|
{{- end}}
|
|
{{- end}}
|
|
}
|
|
|
|
} // namespace interop
|
|
} // namespace wgpu
|
|
|
|
|
|
{{- /*
|
|
--------------------------------------------------------------------------------
|
|
-- Wrappers emits the C++ 'Wrappers' class, which holds all the interface and
|
|
-- namespace interop wrapper classes.
|
|
--------------------------------------------------------------------------------
|
|
*/ -}}
|
|
{{- define "Wrappers"}}
|
|
// Wrappers holds all the Napi class constructors, and Napi::ObjectWrap type
|
|
// declarations, for each of the WebIDL interface and namespace types.
|
|
class Wrappers {
|
|
Wrappers(Napi::Env env) {
|
|
{{- range $ := .Declarations}}
|
|
{{- if IsInterfaceOrNamespace $}}
|
|
{{$.Name}}_ctor = Napi::Persistent(W{{$.Name}}::Class(env));
|
|
{{- end}}
|
|
{{- end}}
|
|
}
|
|
|
|
static Wrappers* instance;
|
|
|
|
public:
|
|
{{- range $ := .Declarations}}
|
|
{{- if IsInterfaceOrNamespace $}}{{template "Wrapper" $}}
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
// Allocates and constructs the Wrappers instance
|
|
static Wrappers* Init(Napi::Env env) {
|
|
instance = new Wrappers(env);
|
|
return instance;
|
|
}
|
|
|
|
// Destructs and frees the Wrappers instance
|
|
static void Term(Napi::Env env) {
|
|
delete instance;
|
|
instance = nullptr;
|
|
}
|
|
|
|
static Wrappers* For(Napi::Env env) {
|
|
// Currently Napi only actually supports a single Env, so there's no point
|
|
// maintaining a map of Env to Wrapper. Note: This might not always be true.
|
|
return instance;
|
|
}
|
|
|
|
{{ range $ := .Declarations}}
|
|
{{- if IsInterfaceOrNamespace $}}
|
|
Napi::FunctionReference {{$.Name}}_ctor;
|
|
{{- end}}
|
|
{{- end}}
|
|
};
|
|
|
|
Wrappers* Wrappers::instance = nullptr;
|
|
{{- end}}
|
|
|
|
|
|
{{- /*
|
|
--------------------------------------------------------------------------------
|
|
-- Wrapper emits the C++ wrapper class for the given ast.Interface or
|
|
-- ast.Namespace.
|
|
-- This wrapper class inherits from Napi::ObjectWrap, which binds the lifetime
|
|
-- of the JavaScript object to the lifetime of the wrapper class instance.
|
|
-- If the wrapper is for an interface, the wrapper object holds a unique_ptr to
|
|
-- the interface implementation, and delegates all exposed method calls on to
|
|
-- the implementation.
|
|
-- See: https://github.com/nodejs/node-addon-api/blob/main/doc/object_wrap.md
|
|
--------------------------------------------------------------------------------
|
|
*/ -}}
|
|
{{- define "Wrapper"}}
|
|
struct W{{$.Name}} : public Napi::ObjectWrap<W{{$.Name}}> {
|
|
{{- $attributes := FlattenedAttributesOf $ }}
|
|
{{- $constants := FlattenedConstantsOf $ }}
|
|
{{- $methods := FlattenedMethodsOf $ }}
|
|
{{- if IsInterface $}}
|
|
std::unique_ptr<{{$.Name}}> impl;
|
|
{{- end}}
|
|
static Napi::Function Class(Napi::Env env) {
|
|
return DefineClass(env, "{{$.Name}}", {
|
|
{{ if $s := SetlikeOf $}}
|
|
InstanceMethod("has", &W{{$.Name}}::has),
|
|
InstanceMethod("keys", &W{{$.Name}}::keys),
|
|
{{- end}}
|
|
{{- range $m := $methods}}
|
|
InstanceMethod("{{$m.Name}}", &W{{$.Name}}::{{$m.Name}}),
|
|
{{- end}}
|
|
{{- range $a := $attributes}}
|
|
{{- if not (HasAnnotation $a "SameObject")}}
|
|
InstanceAccessor("{{$a.Name}}", &W{{$.Name}}::get{{Title $a.Name}},
|
|
{{- if $a.Readonly}} nullptr{{else}} &W{{$.Name}}::set{{Title $a.Name}}{{end -}}
|
|
),
|
|
{{- end}}
|
|
{{- end}}
|
|
{{- range $c := $constants}}
|
|
StaticValue("{{$c.Name}}", ToJS(env, {{$.Name}}::{{$c.Name}}), napi_default_jsproperty),
|
|
{{- end}}
|
|
});
|
|
}
|
|
|
|
W{{$.Name}}(const Napi::CallbackInfo& info) : ObjectWrap(info) {}
|
|
|
|
{{- if $s := SetlikeOf $}}
|
|
Napi::Value has(const Napi::CallbackInfo& info) {
|
|
std::tuple<{{template "Type" $s.Elem}}> args;
|
|
auto res = FromJS(info, args);
|
|
if (res) {
|
|
return ToJS(info.Env(), impl->has(info.Env(), std::get<0>(args)));
|
|
}
|
|
Napi::TypeError::New(info.Env(), res.error).ThrowAsJavaScriptException();
|
|
return {};
|
|
}
|
|
Napi::Value keys(const Napi::CallbackInfo& info) {
|
|
return ToJS(info.Env(), impl->keys(info.Env()));
|
|
}
|
|
{{- end}}
|
|
{{- range $m := $methods}}
|
|
Napi::Value {{$m.Name}}(const Napi::CallbackInfo& info) {
|
|
std::string error;
|
|
{{- range $overload_idx, $o := $m.Overloads}}
|
|
{{- $overloaded := gt (len $m.Overloads) 1}}
|
|
{ {{if $overloaded}}// Overload {{$overload_idx}}{{end}}
|
|
std::tuple<
|
|
{{- range $i, $p := $o.Parameters}}
|
|
{{- if $i}}, {{end}}
|
|
{{- if $p.Init }}DefaultedParameter<{{template "Type" $p.Type}}>
|
|
{{- else if $p.Optional}}std::optional<{{template "Type" $p.Type}}>
|
|
{{- else }}{{template "Type" $p.Type}}
|
|
{{- end}}
|
|
{{- end}}> args;
|
|
|
|
{{- range $i, $p := $o.Parameters}}
|
|
{{- if $p.Init}}
|
|
std::get<{{$i}} /* {{$p.Name}} */>(args).default_value = {{Eval "Literal" "Value" $p.Init "Type" $p.Type}};
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
auto res = FromJS(info, args);
|
|
if (res) {
|
|
{{/* indent */}}INTEROP_LOG(
|
|
{{- range $i, $p := $o.Parameters}}
|
|
{{- if $i}}, ", {{$p.Name}}: "{{else}}"{{$p.Name}}: "{{end}}, std::get<{{$i}}>(args)
|
|
{{- end}});
|
|
{{/* indent */}}
|
|
{{- if not (IsUndefinedType $o.Type) }}auto result = {{end -}}
|
|
impl->{{$o.Name}}(info.Env(){{range $i, $_ := $o.Parameters}}, std::get<{{$i}}>(args){{end}});
|
|
{{/* indent */ -}}
|
|
{{- if IsUndefinedType $o.Type}}return info.Env().Undefined();
|
|
{{- else }}return ToJS(info.Env(), result);
|
|
{{- end }}
|
|
}
|
|
error = {{if $overloaded}}"\noverload {{$overload_idx}} failed to match:\n" + {{end}}res.error;
|
|
}
|
|
{{- end}}
|
|
Napi::TypeError::New(info.Env(), "no overload matched for {{$m.Name}}:\n" + error).ThrowAsJavaScriptException();
|
|
return {};
|
|
}
|
|
{{- end}}
|
|
|
|
{{- range $a := $attributes}}
|
|
{{- if not (HasAnnotation $a "SameObject")}}
|
|
Napi::Value get{{Title $a.Name}}(const Napi::CallbackInfo& info) {
|
|
return ToJS(info.Env(), impl->get{{Title $a.Name}}(info.Env()));
|
|
}
|
|
{{- if not $a.Readonly}}
|
|
void set{{Title $a.Name}}(const Napi::CallbackInfo& info, const Napi::Value& value) {
|
|
{{template "Type" $a.Type}} v{};
|
|
auto res = FromJS(info.Env(), value, v);
|
|
if (res) {
|
|
impl->set{{Title $a.Name}}(info.Env(), std::move(v));
|
|
} else {
|
|
res = res.Append("invalid value to {{$a.Name}}");
|
|
Napi::TypeError::New(info.Env(), res.error).ThrowAsJavaScriptException();
|
|
}
|
|
}
|
|
{{- end}}
|
|
{{- end}}
|
|
{{- end}}
|
|
};
|
|
{{end}}
|
|
|
|
|
|
{{- /*
|
|
--------------------------------------------------------------------------------
|
|
-- Dictionary emits the C++ method implementations and associated functions of
|
|
-- the interop type that defines the given ast.Dictionary
|
|
--------------------------------------------------------------------------------
|
|
*/ -}}
|
|
{{- define "Dictionary"}}
|
|
Result Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
|
|
auto object = value.ToObject();
|
|
Result res;
|
|
{{- template "DictionaryMembersFromJS" $}};
|
|
return Success;
|
|
}
|
|
|
|
Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) {
|
|
auto object = Napi::Object::New(env);
|
|
{{- template "DictionaryMembersToJS" $}}
|
|
return object;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& o, const {{$.Name}}& dict) {
|
|
o << "{{$.Name}} {";
|
|
{{- range $i, $m := $.Members}}
|
|
o << {{if $i}}", "{{else}}" "{{end}} << "{{$m.Name}}: ";
|
|
utils::Write(o, dict.{{$m.Name}});
|
|
{{- end }}
|
|
o << "}" << std::endl;
|
|
return o;
|
|
}
|
|
{{ end}}
|
|
|
|
|
|
{{- /*
|
|
--------------------------------------------------------------------------------
|
|
-- DictionaryMembersFromJS emits the C++ logic to convert each of the
|
|
-- dictionary ast.Member fields from JavaScript to C++. Each call to ToJS() is
|
|
-- emitted as a separate statement, and requires a 'Result res' local to be
|
|
-- declared
|
|
--------------------------------------------------------------------------------
|
|
*/ -}}
|
|
{{- define "DictionaryMembersFromJS"}}
|
|
{{- if $.Inherits}}{{template "DictionaryMembersFromJS" (Lookup $.Inherits)}}{{end}}
|
|
{{- range $i, $m := $.Members}}
|
|
{{/* indent */}}
|
|
{{- if $m.Init }}res = interop::FromJSOptional(env, object.Get("{{$m.Name}}"), out.{{$m.Name}});
|
|
{{- else }}res = interop::FromJS(env, object.Get("{{$m.Name}}"), out.{{$m.Name}});
|
|
{{- end }}
|
|
if (!res) {
|
|
return res.Append("while converting member '{{$m.Name}}'");
|
|
}
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
|
|
{{- /*
|
|
--------------------------------------------------------------------------------
|
|
-- DictionaryMembersToJS emits the C++ logic to convert each of the
|
|
-- dictionary ast.Member fields to JavaScript from C++. Each call to ToJS() is
|
|
-- emitted as a separate statement
|
|
--------------------------------------------------------------------------------
|
|
*/ -}}
|
|
{{- define "DictionaryMembersToJS"}}
|
|
{{- if $.Inherits}}{{template "DictionaryMembersToJS" (Lookup $.Inherits)}}{{end}}
|
|
{{- range $m := $.Members}}
|
|
object.Set(Napi::String::New(env, "{{$m.Name}}"), interop::ToJS(env, value.{{$m.Name}}));
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
|
|
{{- /*
|
|
--------------------------------------------------------------------------------
|
|
-- Interface emits the C++ method implementations that define the given
|
|
-- ast.Interface.
|
|
-- Note: Most of the actual binding logic lives in the interface wrapper class.
|
|
--------------------------------------------------------------------------------
|
|
*/ -}}
|
|
{{- define "Interface"}}
|
|
{{$.Name}}::{{$.Name}}() = default;
|
|
|
|
{{$.Name}}* {{$.Name}}::Unwrap(Napi::Object object) {
|
|
auto* wrappers = Wrappers::For(object.Env());
|
|
if (!object.InstanceOf(wrappers->{{$.Name}}_ctor.Value())) {
|
|
return nullptr;
|
|
}
|
|
return Wrappers::W{{$.Name}}::Unwrap(object)->impl.get();
|
|
}
|
|
|
|
Interface<{{$.Name}}> {{$.Name}}::Bind(Napi::Env env, std::unique_ptr<{{$.Name}}>&& impl) {
|
|
auto* wrappers = Wrappers::For(env);
|
|
auto object = wrappers->{{$.Name}}_ctor.New({});
|
|
auto* wrapper = Wrappers::W{{$.Name}}::Unwrap(object);
|
|
wrapper->impl = std::move(impl);
|
|
|
|
{{- /*Add the [SameObject] members as read-only property on the JS object.*/ -}}
|
|
{{- range $a := AttributesOf $}}
|
|
{{- if HasAnnotation $a "SameObject"}}
|
|
object.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
"{{$a.Name}}", ToJS(env, wrapper->impl->get{{Title $a.Name}}(env))
|
|
));
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
return Interface<{{$.Name}}>(object);
|
|
}
|
|
|
|
{{$.Name}}::~{{$.Name}}() = default;
|
|
{{ end}}
|
|
|
|
|
|
{{- /*
|
|
--------------------------------------------------------------------------------
|
|
-- Enum emits the C++ associated functions of the interop type that defines the
|
|
-- given ast.Enum
|
|
--------------------------------------------------------------------------------
|
|
*/ -}}
|
|
{{- define "Enum"}}
|
|
bool Converter<{{$.Name}}>::FromString(std::string str, {{$.Name}}& out) {
|
|
{{- range $e := $.Values}}
|
|
if (str == {{$e.Value}}) {
|
|
out = {{$.Name}}::{{EnumEntryName $e.Value}};
|
|
return true;
|
|
}
|
|
{{- end}}
|
|
return false;
|
|
}
|
|
|
|
const char* Converter<{{$.Name}}>::ToString({{$.Name}} value) {
|
|
switch (value) {
|
|
{{- range $e := $.Values}}
|
|
case {{$.Name}}::{{EnumEntryName $e.Value}}:
|
|
return {{$e.Value}};
|
|
{{- end}}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Result Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
|
|
std::string str = value.ToString();
|
|
if (FromString(str, out)) {
|
|
return Success;
|
|
}
|
|
return Error(str + " is not a valid enum value of {{$.Name}}");
|
|
}
|
|
|
|
Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) {
|
|
switch (value) {
|
|
{{- range $e := $.Values}}
|
|
case {{$.Name}}::{{EnumEntryName $e.Value}}:
|
|
return Napi::String::New(env, {{$e.Value}});
|
|
{{- end}}
|
|
}
|
|
return env.Undefined();
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& o, {{$.Name}} value) {
|
|
if (auto* s = Converter<{{$.Name}}>::ToString(value)) {
|
|
return o << s;
|
|
}
|
|
return o << "undefined<{{$.Name}}>";
|
|
}
|
|
|
|
{{end}}
|