| {{/* |
| 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 src/dawn_node/tools/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 |
| * src/dawn_node/tools/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 $}} |
| global.Set(Napi::String::New(env, "{{$.Name}}"), wrapper->{{$.Name}}_ctor.Value()); |
| {{- 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}}> { |
| {{- 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 := MethodsOf $}} |
| InstanceMethod("{{$m.Name}}", &W{{$.Name}}::{{$m.Name}}), |
| {{- end}} |
| {{- range $a := AttributesOf $}} |
| InstanceAccessor("{{$a.Name}}", &W{{$.Name}}::get{{Title $a.Name}}, |
| {{- if $a.Readonly}} nullptr{{else}} &W{{$.Name}}::set{{Title $a.Name}}{{end -}} |
| ), |
| {{- end}} |
| {{- range $c := ConstantsOf $}} |
| 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::Error::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 := MethodsOf $}} |
| 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().Null(); |
| {{- else }}return ToJS(info.Env(), result); |
| {{- end }} |
| } |
| error = {{if $overloaded}}"\noverload {{$overload_idx}} failed to match:\n" + {{end}}res.error; |
| } |
| {{- end}} |
| Napi::Error::New(info.Env(), "no overload matched for {{$m.Name}}:\n" + error).ThrowAsJavaScriptException(); |
| return {}; |
| } |
| {{- end}} |
| |
| {{- range $a := AttributesOf $}} |
| 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::Error::New(info.Env(), res.error).ThrowAsJavaScriptException(); |
| } |
| } |
| {{- 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); |
| 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}} |