| {{/* | |
| Copyright 2021 The Dawn & Tint Authors | |
| Redistribution and use in source and binary forms, with or without | |
| modification, are permitted provided that the following conditions are met: | |
| 1. Redistributions of source code must retain the above copyright notice, this | |
| list of conditions and the following disclaimer. | |
| 2. Redistributions in binary form must reproduce the above copyright notice, | |
| this list of conditions and the following disclaimer in the documentation | |
| and/or other materials provided with the distribution. | |
| 3. Neither the name of the copyright holder nor the names of its | |
| contributors may be used to endorse or promote products derived from | |
| this software without specific prior written permission. | |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */}} | |
| {{- /* | |
| -------------------------------------------------------------------------------- | |
| 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" | |
| #include "src/dawn/node/binding/Errors.h" | |
| namespace wgpu { | |
| namespace interop { | |
| namespace { | |
| // This is passed to constructors so that constructor can | |
| // check if it's being called legitimately from C++ or | |
| // illegitimately from JavaScript. | |
| static void* kInternalConstructionMarker = nullptr; | |
| {{template "Wrappers" $}} | |
| } // namespace | |
| const Constructors* ConstructorsFor(Napi::Env env) { | |
| return Wrappers::For(env); | |
| } | |
| {{ range $ := .Declarations}} | |
| {{- if IsDictionary $}}{{template "Dictionary" $}} | |
| {{- else if IsInterface $}}{{template "Interface" $}} | |
| {{- else if IsEnum $}}{{template "Enum" $}} | |
| {{- end}} | |
| {{- end}} | |
| Napi::Object Initialize(Napi::Env env) { | |
| auto* wrapper = Wrappers::Init(env); | |
| auto exports = Napi::Object::New(env); | |
| {{- range $ := .Declarations}} | |
| {{- if IsInterfaceOrNamespace $}} | |
| {{- if not (HasAnnotation $ "LegacyNoInterfaceObject")}} | |
| exports.Set(Napi::String::New(env, "{{$.Name}}"), wrapper->{{$.Name}}_ctor.Value()); | |
| {{- end}} | |
| {{- end}} | |
| {{- end}} | |
| return exports; | |
| } | |
| } // 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 : public Constructors { | |
| explicit Wrappers(Napi::Env env) : Constructors(env) { | |
| {{- range $ := .Declarations}} | |
| {{- if and (IsInterfaceOrNamespace $) (not (HasAnnotation $ "ManualImplementation"))}} | |
| {{- if and (IsInterface $) ($.Inherits)}} | |
| { | |
| // It's an interface and it inherits from another class, pass the class it inherits | |
| // from to its Class factory | |
| Napi::Value baseClassValue = {{$.Inherits}}_ctor.Value(); | |
| {{$.Name}}_ctor = Napi::Persistent(W{{$.Name}}::Class(env, baseClassValue)); | |
| } | |
| {{- else}} | |
| {{$.Name}}_ctor = Napi::Persistent(W{{$.Name}}::Class(env)); | |
| {{- end}} | |
| {{- end}} | |
| {{- end}} | |
| } | |
| public: | |
| {{- range $ := .Declarations}} | |
| {{- if and (IsInterfaceOrNamespace $) (not (HasAnnotation $ "ManualImplementation"))}} | |
| {{template "Wrapper" $}} | |
| {{- end}} | |
| {{- end}} | |
| static void Delete(Napi::Env, Wrappers* data) { | |
| delete data; | |
| } | |
| // Allocates and constructs the Wrappers instance | |
| static Wrappers* Init(Napi::Env env) { | |
| auto instance = new Wrappers(env); | |
| env.SetInstanceData<Wrappers, &Wrappers::Delete>(instance); | |
| return instance; | |
| } | |
| // Destructs and frees the Wrappers instance | |
| static void Term(Napi::Env env) { | |
| env.SetInstanceData<Wrappers>(nullptr); | |
| } | |
| static Constructors* For(Napi::Env env) { | |
| return env.GetInstanceData<Wrappers>(); | |
| } | |
| }; | |
| {{- 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}} | |
| {{- if and (IsInterface $) ($.Inherits) }} | |
| // This class inherits from another, require the class factory to | |
| // receive the base class so it can setup inheritance. | |
| static Napi::Function Class(Napi::Env env, Napi::Value baseClassValue) { | |
| {{- else}} | |
| static Napi::Function Class(Napi::Env env) { | |
| {{- end}} | |
| auto func = DefineClass(env, "{{$.Name}}", { | |
| // WebIDL spec section 3 defines the property for %Symbol.toStringTag% | |
| InstanceValue(Napi::Symbol::WellKnown(env, "toStringTag"), Napi::String::New(env, "{{$.Name}}"), napi_configurable), | |
| {{ if $s := SetlikeOf $}} | |
| InstanceMethod("has", &W{{$.Name}}::has), | |
| InstanceMethod("keys", &W{{$.Name}}::keys), | |
| InstanceAccessor("size", &W{{$.Name}}::getSize, nullptr), | |
| InstanceMethod(Napi::Symbol::WellKnown(env, "iterator"), &W{{$.Name}}::iterator), | |
| {{- end}} | |
| {{- range $m := $methods}} | |
| InstanceMethod("{{$m.Name}}", &W{{$.Name}}::{{$m.Name}}, static_cast<napi_property_attributes>(napi_default_jsproperty)), | |
| {{- 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 -}} | |
| napi_default_jsproperty), | |
| {{- end}} | |
| {{- end}} | |
| {{- range $c := $constants}} | |
| StaticValue("{{$c.Name}}", ToJS(env, {{$.Name}}::{{$c.Name}}), napi_default_jsproperty), | |
| {{- end}} | |
| }); | |
| {{- if and (IsInterface $) ($.Inherits)}} | |
| // It inherits from another class, setup the prototype chain. | |
| ChainPrototype(baseClassValue, func); | |
| {{- end}} | |
| return func; | |
| } | |
| explicit W{{$.Name}}(const Napi::CallbackInfo& info) : ObjectWrap(info) { | |
| {{- if IsInterface $}} | |
| {{- if HasConstructor $}} | |
| {{- range $m := $.Members}} | |
| {{- if IsInitializer $m}} | |
| { | |
| {{template "ParameterTupleDecl" $m}} | |
| auto res = FromJS(info, args); | |
| if (res) { | |
| impl = std::make_unique<wgpu::binding::{{$.Name}}>(args); | |
| } | |
| } | |
| // No constructor matched. | |
| if (impl == nullptr) { | |
| Napi::TypeError::New(info.Env(), "No constructor matched.").ThrowAsJavaScriptException(); | |
| } | |
| {{- end}} | |
| {{- end}} | |
| {{- else}} | |
| // Check if the first argument is our internal Napi::External<void> marker | |
| // to prevent JavaScript from constructing this non-constructable object. | |
| if (info.Length() < 1 || !info[0].IsExternal() || info[0].As<Napi::External<void>>().Data() != &kInternalConstructionMarker) { | |
| Napi::TypeError::New(info.Env(), "Failed to construct '{{$.Name}}': Illegal constructor").ThrowAsJavaScriptException(); | |
| } | |
| {{- end}} | |
| {{- end}} | |
| } | |
| {{- 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())); | |
| } | |
| Napi::Value getSize(const Napi::CallbackInfo& info) { | |
| return ToJS(info.Env(), impl->getSize(info.Env())); | |
| } | |
| Napi::Value iterator(const Napi::CallbackInfo& info) { | |
| return impl->iterator(info); | |
| } | |
| {{- end}} | |
| {{- range $m := $methods}} | |
| {{- $implSuffix := ""}} | |
| {{- if ReturnsPromise $m}} | |
| {{- $implSuffix = "Inner"}} | |
| Napi::Value {{$m.Name}}(const Napi::CallbackInfo& info) { | |
| return CatchExceptionIntoPromise(info.Env(), [&]() -> auto { return {{$m.Name}}Inner(info); }); | |
| } | |
| {{- end}} | |
| Napi::Value {{$m.Name}}{{$implSuffix}}(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}} | |
| {{template "ParameterTupleDecl" $o}} | |
| 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}} | |
| {{- /* | |
| -------------------------------------------------------------------------------- | |
| -- Emits a tuple "args" for function argument conversion from JS to C++ | |
| -------------------------------------------------------------------------------- | |
| */ -}} | |
| {{- define "ParameterTupleDecl"}} | |
| std::tuple< | |
| {{- range $i, $p := $.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 := $.Parameters}} | |
| {{- if $p.Init}} | |
| std::get<{{$i}} /* {{$p.Name}} */>(args).default_value = {{Eval "Literal" "Value" $p.Init "Type" $p.Type}}; | |
| {{- 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 << "}\n"; | |
| 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}}'"); | |
| } | |
| {{- else}} | |
| std::ignore = object; | |
| {{- 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; | |
| {{- if not (HasAnnotation $ "ManualImplementation")}} | |
| {{$.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); | |
| // Pass in kInternalConstructionMarker to allow this instance to actually be constructed. | |
| auto object = wrappers->{{$.Name}}_ctor.New({ | |
| Napi::External<void>::New(env, &kInternalConstructionMarker) | |
| }); | |
| 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)), napi_default_jsproperty | |
| )); | |
| {{- end}} | |
| {{- end}} | |
| return Interface<{{$.Name}}>(object); | |
| } | |
| {{- end}} | |
| {{$.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}} |