Implement NativeExtension pointer arguments

* Allows calling into native extensions directly with a pointer
* Makes it easier to implement some APIs more efficiently
* Appears with a "*" in the documentation for the argument.
* Implementing the pointer handling is entirely up to the implementation, although the extension API provides some hint.
* AudioStream has been implemented as an example, allowing to create NativeExtension based AudioStreams.
This commit is contained in:
reduz 2021-08-23 14:53:27 -03:00
parent 679b9be9d3
commit 44d62a9f4b
18 changed files with 370 additions and 25 deletions

View File

@ -558,6 +558,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_PATH_VALID_TYPES);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_SAVE_FILE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX);

View File

@ -31,7 +31,14 @@
#include "doc_data.h"
void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo) {
if (p_retinfo.type == Variant::INT && p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
if (p_retinfo.type == Variant::INT && p_retinfo.hint == PROPERTY_HINT_INT_IS_POINTER) {
p_method.return_type = p_retinfo.hint_string;
if (p_method.return_type == "") {
p_method.return_type = "void*";
} else {
p_method.return_type += "*";
}
} else if (p_retinfo.type == Variant::INT && p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
p_method.return_enum = p_retinfo.class_name;
if (p_method.return_enum.begins_with("_")) { //proxy class
p_method.return_enum = p_method.return_enum.substr(1, p_method.return_enum.length());
@ -55,7 +62,14 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper
void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo) {
p_argument.name = p_arginfo.name;
if (p_arginfo.type == Variant::INT && p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
if (p_arginfo.type == Variant::INT && p_arginfo.hint == PROPERTY_HINT_INT_IS_POINTER) {
p_argument.type = p_arginfo.hint_string;
if (p_argument.type == "") {
p_argument.type = "void*";
} else {
p_argument.type += "*";
}
} else if (p_arginfo.type == Variant::INT && p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
p_argument.enumeration = p_arginfo.class_name;
if (p_argument.enumeration.begins_with("_")) { //proxy class
p_argument.enumeration = p_argument.enumeration.substr(1, p_argument.enumeration.length());

View File

@ -39,6 +39,13 @@
#ifdef TOOLS_ENABLED
static String get_type_name(const PropertyInfo &p_info) {
if (p_info.type == Variant::INT && (p_info.hint == PROPERTY_HINT_INT_IS_POINTER)) {
if (p_info.hint_string == "") {
return "void*";
} else {
return p_info.hint_string + "*";
}
}
if (p_info.type == Variant::INT && (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM)) {
return String("enum::") + String(p_info.class_name);
}
@ -831,6 +838,20 @@ Dictionary NativeExtensionAPIDump::generate_extension_api() {
}
}
{
Array native_structures;
{
Dictionary d;
d["name"] = "AudioFrame";
d["format"] = "float left,float right";
native_structures.push_back(d);
}
api_dump["native_structures"] = native_structures;
}
return api_dump;
}

View File

@ -2,7 +2,7 @@ proto = """
#define GDVIRTUAL$VER($RET m_name $ARG) \\
StringName _gdvirtual_##m_name##_sn = #m_name;\\
GDNativeExtensionClassCallVirtual _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, #m_name) : (GDNativeExtensionClassCallVirtual) nullptr;\\
bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\
_FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\
ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\
if (script_instance) {\\
Callable::CallError ce; \\
@ -23,7 +23,7 @@ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\
\\
return false;\\
}\\
bool _gdvirtual_##m_name##_overriden() const { \\
_FORCE_INLINE_ bool _gdvirtual_##m_name##_overriden() const { \\
ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\
if (script_instance) {\\
return script_instance->has_method(_gdvirtual_##m_name##_sn);\\
@ -42,7 +42,6 @@ _FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() { \\
return method_info;\\
}
"""

View File

@ -97,6 +97,7 @@ enum PropertyHint {
PROPERTY_HINT_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog
PROPERTY_HINT_INT_IS_OBJECTID,
PROPERTY_HINT_ARRAY_TYPE,
PROPERTY_HINT_INT_IS_POINTER,
PROPERTY_HINT_MAX,
// When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit
};

View File

@ -191,6 +191,7 @@ struct PtrToArg<ObjectID> {
// This is for the special cases used by Variant.
// No EncodeT because direct pointer conversion not possible.
#define MAKE_VECARG(m_type) \
template <> \
struct PtrToArg<Vector<m_type>> { \
@ -236,6 +237,7 @@ struct PtrToArg<ObjectID> {
} \
}
// No EncodeT because direct pointer conversion not possible.
#define MAKE_VECARG_ALT(m_type, m_type_alt) \
template <> \
struct PtrToArg<Vector<m_type_alt>> { \
@ -285,6 +287,7 @@ MAKE_VECARG_ALT(String, StringName);
// For stuff that gets converted to Array vectors.
// No EncodeT because direct pointer conversion not possible.
#define MAKE_VECARR(m_type) \
template <> \
struct PtrToArg<Vector<m_type>> { \
@ -325,6 +328,7 @@ MAKE_VECARR(Variant);
MAKE_VECARR(RID);
MAKE_VECARR(Plane);
// No EncodeT because direct pointer conversion not possible.
#define MAKE_DVECARR(m_type) \
template <> \
struct PtrToArg<Vector<m_type>> { \
@ -372,6 +376,7 @@ MAKE_VECARR(Plane);
// Special case for IPAddress.
// No EncodeT because direct pointer conversion not possible.
#define MAKE_STRINGCONV_BY_REFERENCE(m_type) \
template <> \
struct PtrToArg<m_type> { \
@ -395,6 +400,7 @@ MAKE_VECARR(Plane);
MAKE_STRINGCONV_BY_REFERENCE(IPAddress);
// No EncodeT because direct pointer conversion not possible.
template <>
struct PtrToArg<Vector<Face3>> {
_FORCE_INLINE_ static Vector<Face3> convert(const void *p_ptr) {
@ -429,6 +435,7 @@ struct PtrToArg<Vector<Face3>> {
}
};
// No EncodeT because direct pointer conversion not possible.
template <>
struct PtrToArg<const Vector<Face3> &> {
_FORCE_INLINE_ static Vector<Face3> convert(const void *p_ptr) {

130
core/variant/native_ptr.h Normal file
View File

@ -0,0 +1,130 @@
/*************************************************************************/
/* native_ptr.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef NATIVE_PTR_H
#define NATIVE_PTR_H
#include "core/math/audio_frame.h"
#include "core/variant/method_ptrcall.h"
#include "core/variant/type_info.h"
template <class T>
struct GDNativeConstPtr {
const T *data = nullptr;
GDNativeConstPtr(const T *p_assign) { data = p_assign; }
static const char *get_name() { return "const void"; }
operator const T *() const { return data; }
operator Variant() const { return uint64_t(data); }
};
template <class T>
struct GDNativePtr {
T *data = nullptr;
GDNativePtr(T *p_assign) { data = p_assign; }
static const char *get_name() { return "void"; }
operator T *() const { return data; }
operator Variant() const { return uint64_t(data); }
};
#define GDVIRTUAL_NATIVE_PTR(m_type) \
template <> \
struct GDNativeConstPtr<m_type> { \
const m_type *data = nullptr; \
GDNativeConstPtr(const m_type *p_assign) { data = p_assign; } \
static const char *get_name() { return "const " #m_type; } \
operator const m_type *() const { return data; } \
operator Variant() const { return uint64_t(data); } \
}; \
template <> \
struct GDNativePtr<m_type> { \
m_type *data = nullptr; \
GDNativePtr(m_type *p_assign) { data = p_assign; } \
static const char *get_name() { return #m_type; } \
operator m_type *() const { return data; } \
operator Variant() const { return uint64_t(data); } \
};
template <class T>
struct GetTypeInfo<GDNativeConstPtr<T>> {
static const Variant::Type VARIANT_TYPE = Variant::NIL;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
static inline PropertyInfo get_class_info() {
return PropertyInfo(Variant::INT, String(), PROPERTY_HINT_INT_IS_POINTER, GDNativeConstPtr<T>::get_name());
}
};
template <class T>
struct GetTypeInfo<GDNativePtr<T>> {
static const Variant::Type VARIANT_TYPE = Variant::NIL;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
static inline PropertyInfo get_class_info() {
return PropertyInfo(Variant::INT, String(), PROPERTY_HINT_INT_IS_POINTER, GDNativePtr<T>::get_name());
}
};
template <class T>
struct PtrToArg<GDNativeConstPtr<T>> {
_FORCE_INLINE_ static GDNativeConstPtr<T> convert(const void *p_ptr) {
return GDNativeConstPtr<T>(reinterpret_cast<const T *>(p_ptr));
}
typedef const T *EncodeT;
_FORCE_INLINE_ static void encode(GDNativeConstPtr<T> p_val, void *p_ptr) {
*((const T **)p_ptr) = p_val.data;
}
};
template <class T>
struct PtrToArg<GDNativePtr<T>> {
_FORCE_INLINE_ static GDNativePtr<T> convert(const void *p_ptr) {
return GDNativePtr<T>(reinterpret_cast<const T *>(p_ptr));
}
typedef T *EncodeT;
_FORCE_INLINE_ static void encode(GDNativePtr<T> p_val, void *p_ptr) {
*((T **)p_ptr) = p_val.data;
}
};
GDVIRTUAL_NATIVE_PTR(AudioFrame)
GDVIRTUAL_NATIVE_PTR(bool)
GDVIRTUAL_NATIVE_PTR(char)
GDVIRTUAL_NATIVE_PTR(char16_t)
GDVIRTUAL_NATIVE_PTR(char32_t)
GDVIRTUAL_NATIVE_PTR(wchar_t)
GDVIRTUAL_NATIVE_PTR(uint8_t)
GDVIRTUAL_NATIVE_PTR(int8_t)
GDVIRTUAL_NATIVE_PTR(uint16_t)
GDVIRTUAL_NATIVE_PTR(int16_t)
GDVIRTUAL_NATIVE_PTR(uint32_t)
GDVIRTUAL_NATIVE_PTR(int32_t)
GDVIRTUAL_NATIVE_PTR(int64_t)
GDVIRTUAL_NATIVE_PTR(uint64_t)
GDVIRTUAL_NATIVE_PTR(float)
GDVIRTUAL_NATIVE_PTR(double)
#endif // NATIVE_PTR_H

View File

@ -2351,9 +2351,11 @@
</constant>
<constant name="PROPERTY_HINT_INT_IS_OBJECTID" value="38" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_INT_IS_POINTER" value="40" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_ARRAY_TYPE" value="39" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_MAX" value="40" enum="PropertyHint">
<constant name="PROPERTY_HINT_MAX" value="41" enum="PropertyHint">
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags">
</constant>

View File

@ -13,6 +13,21 @@
<link title="Audio Spectrum Demo">https://godotengine.org/asset-library/asset/528</link>
</tutorials>
<methods>
<method name="_get_length" qualifiers="virtual const">
<return type="float" />
<description>
</description>
</method>
<method name="_get_stream_name" qualifiers="virtual const">
<return type="String" />
<description>
</description>
</method>
<method name="_instance_playback" qualifiers="virtual const">
<return type="AudioStreamPlayback" />
<description>
</description>
</method>
<method name="get_length" qualifiers="const">
<return type="float" />
<description>

View File

@ -10,6 +10,46 @@
<link title="Audio Generator Demo">https://godotengine.org/asset-library/asset/526</link>
</tutorials>
<methods>
<method name="_get_loop_count" qualifiers="virtual const">
<return type="int" />
<description>
</description>
</method>
<method name="_get_playback_position" qualifiers="virtual const">
<return type="float" />
<description>
</description>
</method>
<method name="_is_playing" qualifiers="virtual const">
<return type="bool" />
<description>
</description>
</method>
<method name="_mix" qualifiers="virtual">
<return type="void" />
<argument index="0" name="buffer" type="AudioFrame*" />
<argument index="1" name="rate_scale" type="float" />
<argument index="2" name="frames" type="int" />
<description>
</description>
</method>
<method name="_seek" qualifiers="virtual">
<return type="void" />
<argument index="0" name="position" type="float" />
<description>
</description>
</method>
<method name="_start" qualifiers="virtual">
<return type="void" />
<argument index="0" name="from_pos" type="float" />
<description>
</description>
</method>
<method name="_stop" qualifiers="virtual">
<return type="void" />
<description>
</description>
</method>
</methods>
<constants>
</constants>

View File

@ -1007,6 +1007,8 @@ def format_table(f, data, remove_empty_columns=False): # type: (TextIO, Iterabl
def make_type(klass, state): # type: (str, State) -> str
if klass.find("*") != -1: # Pointer, ignore
return klass
link_type = klass
if link_type.endswith("[]"): # Typed array, strip [] to link to contained type.
link_type = link_type[:-2]

View File

@ -170,7 +170,7 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum) {
if (t.is_empty()) {
t = "void";
}
bool can_ref = (t != "void") || !p_enum.is_empty();
bool can_ref = (t != "void" && t.find("*") == -1) || !p_enum.is_empty();
if (!p_enum.is_empty()) {
if (p_enum.get_slice_count(".") > 1) {
@ -632,8 +632,8 @@ void EditorHelp::_update_doc() {
continue;
}
}
// Ignore undocumented private.
if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.is_empty()) {
// Ignore undocumented non virtual private.
if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.is_empty() && cd.methods[i].qualifiers.find("virtual") == -1) {
continue;
}
methods.push_back(cd.methods[i]);

View File

@ -272,8 +272,12 @@ void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) {
}
if (p_stream.is_valid()) {
stream = p_stream;
stream_playback = p_stream->instance_playback();
if (stream_playback.is_valid()) {
stream = p_stream;
} else {
stream.unref();
}
}
AudioServer::get_singleton()->unlock();

View File

@ -624,8 +624,12 @@ void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) {
}
if (p_stream.is_valid()) {
stream = p_stream;
stream_playback = p_stream->instance_playback();
if (stream_playback.is_valid()) {
stream = p_stream;
} else {
stream.unref();
}
}
AudioServer::get_singleton()->unlock();

View File

@ -202,8 +202,12 @@ void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) {
}
if (p_stream.is_valid()) {
stream = p_stream;
stream_playback = p_stream->instance_playback();
if (stream_playback.is_valid()) {
stream = p_stream;
} else {
stream.unref();
}
}
AudioServer::get_singleton()->unlock();

View File

@ -33,6 +33,63 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
void AudioStreamPlayback::start(float p_from_pos) {
if (GDVIRTUAL_CALL(_start, p_from_pos)) {
return;
}
ERR_FAIL_MSG("AudioStreamPlayback::start unimplemented!");
}
void AudioStreamPlayback::stop() {
if (GDVIRTUAL_CALL(_stop)) {
return;
}
ERR_FAIL_MSG("AudioStreamPlayback::stop unimplemented!");
}
bool AudioStreamPlayback::is_playing() const {
bool ret;
if (GDVIRTUAL_CALL(_is_playing, ret)) {
return ret;
}
ERR_FAIL_V_MSG(false, "AudioStreamPlayback::is_playing unimplemented!");
}
int AudioStreamPlayback::get_loop_count() const {
int ret;
if (GDVIRTUAL_CALL(_get_loop_count, ret)) {
return ret;
}
return 0;
}
float AudioStreamPlayback::get_playback_position() const {
float ret;
if (GDVIRTUAL_CALL(_get_playback_position, ret)) {
return ret;
}
ERR_FAIL_V_MSG(0, "AudioStreamPlayback::get_playback_position unimplemented!");
}
void AudioStreamPlayback::seek(float p_time) {
if (GDVIRTUAL_CALL(_seek, p_time)) {
return;
}
}
void AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
if (GDVIRTUAL_CALL(_mix, p_buffer, p_rate_scale, p_frames)) {
return;
}
WARN_PRINT_ONCE("AudioStreamPlayback::mix unimplemented!");
}
void AudioStreamPlayback::_bind_methods() {
GDVIRTUAL_BIND(_start, "from_pos")
GDVIRTUAL_BIND(_stop)
GDVIRTUAL_BIND(_is_playing)
GDVIRTUAL_BIND(_get_loop_count)
GDVIRTUAL_BIND(_get_playback_position)
GDVIRTUAL_BIND(_seek, "position")
GDVIRTUAL_BIND(_mix, "buffer", "rate_scale", "frames");
}
//////////////////////////////
void AudioStreamPlaybackResampled::_begin_resample() {
@ -92,8 +149,34 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
////////////////////////////////
Ref<AudioStreamPlayback> AudioStream::instance_playback() {
Ref<AudioStreamPlayback> ret;
if (GDVIRTUAL_CALL(_instance_playback, ret)) {
return ret;
}
ERR_FAIL_V_MSG(Ref<AudioStreamPlayback>(), "Method must be implemented!");
}
String AudioStream::get_stream_name() const {
String ret;
if (GDVIRTUAL_CALL(_get_stream_name, ret)) {
return ret;
}
return String();
}
float AudioStream::get_length() const {
float ret;
if (GDVIRTUAL_CALL(_get_length, ret)) {
return ret;
}
return 0;
}
void AudioStream::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_length"), &AudioStream::get_length);
GDVIRTUAL_BIND(_instance_playback);
GDVIRTUAL_BIND(_get_stream_name);
GDVIRTUAL_BIND(_get_length);
}
////////////////////////////////
@ -358,3 +441,4 @@ void AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scal
AudioStreamPlaybackRandomPitch::~AudioStreamPlaybackRandomPitch() {
random_pitch->playbacks.erase(this);
}
/////////////////////////////////////////////

View File

@ -36,20 +36,33 @@
#include "servers/audio/audio_filter_sw.h"
#include "servers/audio_server.h"
#include "core/object/gdvirtual.gen.inc"
#include "core/object/script_language.h"
#include "core/variant/native_ptr.h"
class AudioStreamPlayback : public RefCounted {
GDCLASS(AudioStreamPlayback, RefCounted);
protected:
static void _bind_methods();
GDVIRTUAL1(_start, float)
GDVIRTUAL0(_stop)
GDVIRTUAL0RC(bool, _is_playing)
GDVIRTUAL0RC(int, _get_loop_count)
GDVIRTUAL0RC(float, _get_playback_position)
GDVIRTUAL1(_seek, float)
GDVIRTUAL3(_mix, GDNativePtr<AudioFrame>, float, int)
public:
virtual void start(float p_from_pos = 0.0) = 0;
virtual void stop() = 0;
virtual bool is_playing() const = 0;
virtual void start(float p_from_pos = 0.0);
virtual void stop();
virtual bool is_playing() const;
virtual int get_loop_count() const = 0; //times it looped
virtual int get_loop_count() const; //times it looped
virtual float get_playback_position() const = 0;
virtual void seek(float p_time) = 0;
virtual float get_playback_position() const;
virtual void seek(float p_time);
virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) = 0;
virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
};
class AudioStreamPlaybackResampled : public AudioStreamPlayback {
@ -84,11 +97,15 @@ class AudioStream : public Resource {
protected:
static void _bind_methods();
public:
virtual Ref<AudioStreamPlayback> instance_playback() = 0;
virtual String get_stream_name() const = 0;
GDVIRTUAL0RC(Ref<AudioStreamPlayback>, _instance_playback)
GDVIRTUAL0RC(String, _get_stream_name)
GDVIRTUAL0RC(float, _get_length)
virtual float get_length() const = 0; //if supported, otherwise return 0
public:
virtual Ref<AudioStreamPlayback> instance_playback();
virtual String get_stream_name() const;
virtual float get_length() const;
};
// Microphone

View File

@ -140,8 +140,8 @@ void register_server_types() {
GDREGISTER_VIRTUAL_CLASS(XRInterface);
GDREGISTER_CLASS(XRPositionalTracker);
GDREGISTER_VIRTUAL_CLASS(AudioStream);
GDREGISTER_VIRTUAL_CLASS(AudioStreamPlayback);
GDREGISTER_CLASS(AudioStream);
GDREGISTER_CLASS(AudioStreamPlayback);
GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackResampled);
GDREGISTER_CLASS(AudioStreamMicrophone);
GDREGISTER_CLASS(AudioStreamRandomPitch);