summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/csg/csg_shape.cpp110
-rw-r--r--modules/csg/editor/csg_gizmos.cpp10
-rw-r--r--modules/enet/enet_multiplayer_peer.cpp6
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp35
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h3
-rw-r--r--modules/gdscript/gdscript.cpp138
-rw-r--r--modules/gdscript/gdscript.h2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp30
-rw-r--r--modules/gdscript/gdscript_compiler.cpp104
-rw-r--r--modules/gdscript/gdscript_compiler.h8
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp4
-rw-r--r--modules/gdscript/gdscript_editor.cpp36
-rw-r--r--modules/gdscript/gdscript_function.cpp10
-rw-r--r--modules/gdscript/gdscript_parser.cpp22
-rw-r--r--modules/gdscript/gdscript_parser.h20
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp18
-rw-r--r--modules/gdscript/gdscript_vm.cpp2
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp50
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp12
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp38
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.h1
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp36
-rw-r--r--modules/gdscript/language_server/godot_lsp.h2
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp12
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary.out4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_dictionary.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.out2
-rw-r--r--modules/gltf/config.py1
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml13
-rw-r--r--modules/gltf/doc_classes/GLTFTexture.xml3
-rw-r--r--modules/gltf/doc_classes/GLTFTextureSampler.xml25
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp23
-rw-r--r--modules/gltf/extensions/gltf_light.cpp5
-rw-r--r--modules/gltf/gltf_defines.h2
-rw-r--r--modules/gltf/gltf_document.cpp238
-rw-r--r--modules/gltf/gltf_document.h9
-rw-r--r--modules/gltf/gltf_state.cpp11
-rw-r--r--modules/gltf/gltf_state.h6
-rw-r--r--modules/gltf/register_types.cpp2
-rw-r--r--modules/gltf/structures/gltf_texture.cpp11
-rw-r--r--modules/gltf/structures/gltf_texture.h3
-rw-r--r--modules/gltf/structures/gltf_texture_sampler.cpp (renamed from modules/websocket/websocket_client.h)64
-rw-r--r--modules/gltf/structures/gltf_texture_sampler.h163
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.h2
-rw-r--r--modules/gridmap/grid_map.cpp8
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp8
-rw-r--r--modules/mono/.gitignore3
-rw-r--r--modules/mono/Directory.Build.targets6
-rw-r--r--modules/mono/SdkPackageVersions.props8
-rwxr-xr-xmodules/mono/build_scripts/build_assemblies.py50
-rw-r--r--modules/mono/class_db_api_json.cpp3
-rw-r--r--modules/mono/csharp_script.cpp4
-rw-r--r--modules/mono/csharp_script.h10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs118
-rw-r--r--modules/mono/editor/bindings_generator.cpp6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj2
-rw-r--r--modules/mono/godotsharp_dirs.cpp152
-rw-r--r--modules/mono/godotsharp_dirs.h16
-rw-r--r--modules/multiplayer/scene_replication_config.cpp16
-rw-r--r--modules/noise/noise_texture_2d.cpp18
-rw-r--r--modules/openxr/SCsub1
-rw-r--r--modules/openxr/action_map/openxr_action.cpp6
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp (renamed from modules/websocket/websocket_macros.h)56
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_depth_extension.h (renamed from modules/websocket/emws_client.h)50
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_provider.h1
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper.h3
-rw-r--r--modules/openxr/extensions/openxr_vulkan_extension.cpp76
-rw-r--r--modules/openxr/extensions/openxr_vulkan_extension.h6
-rw-r--r--modules/openxr/openxr_api.cpp305
-rw-r--r--modules/openxr/openxr_api.h31
-rw-r--r--modules/openxr/openxr_interface.cpp42
-rw-r--r--modules/openxr/openxr_interface.h3
-rw-r--r--modules/openxr/util.h84
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct14
-rw-r--r--modules/text_server_adv/gdextension_build/text_server_adv.gdextension24
-rw-r--r--modules/text_server_adv/text_server_adv.cpp4
-rw-r--r--modules/text_server_fb/gdextension_build/SConstruct6
-rw-r--r--modules/text_server_fb/gdextension_build/text_server_fb.gdextension24
-rw-r--r--modules/theora/video_stream_theora.cpp35
-rw-r--r--modules/theora/video_stream_theora.h2
-rw-r--r--modules/websocket/doc_classes/WebSocketClient.xml94
-rw-r--r--modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml84
-rw-r--r--modules/websocket/doc_classes/WebSocketPeer.xml143
-rw-r--r--modules/websocket/doc_classes/WebSocketServer.xml127
-rw-r--r--modules/websocket/editor/editor_debugger_server_websocket.cpp54
-rw-r--r--modules/websocket/editor/editor_debugger_server_websocket.h9
-rw-r--r--modules/websocket/emws_client.cpp159
-rw-r--r--modules/websocket/emws_peer.cpp166
-rw-r--r--modules/websocket/emws_peer.h48
-rw-r--r--modules/websocket/packet_buffer.h58
-rw-r--r--modules/websocket/register_types.cpp23
-rw-r--r--modules/websocket/remote_debugger_peer_websocket.cpp42
-rw-r--r--modules/websocket/remote_debugger_peer_websocket.h7
-rw-r--r--modules/websocket/websocket_client.cpp141
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp425
-rw-r--r--modules/websocket/websocket_multiplayer_peer.h73
-rw-r--r--modules/websocket/websocket_peer.cpp107
-rw-r--r--modules/websocket/websocket_peer.h67
-rw-r--r--modules/websocket/websocket_server.cpp167
-rw-r--r--modules/websocket/websocket_server.h90
-rw-r--r--modules/websocket/wsl_client.cpp407
-rw-r--r--modules/websocket/wsl_client.h91
-rw-r--r--modules/websocket/wsl_peer.cpp847
-rw-r--r--modules/websocket/wsl_peer.h130
-rw-r--r--modules/websocket/wsl_server.cpp329
-rw-r--r--modules/websocket/wsl_server.h98
-rw-r--r--modules/webxr/webxr_interface_js.cpp6
113 files changed, 3252 insertions, 3258 deletions
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 3932c2377f..461960ab26 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -92,13 +92,13 @@ uint32_t CSGShape3D::get_collision_mask() const {
void CSGShape3D::set_collision_layer_value(int p_layer_number, bool p_value) {
ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
- uint32_t collision_layer = get_collision_layer();
+ uint32_t layer = get_collision_layer();
if (p_value) {
- collision_layer |= 1 << (p_layer_number - 1);
+ layer |= 1 << (p_layer_number - 1);
} else {
- collision_layer &= ~(1 << (p_layer_number - 1));
+ layer &= ~(1 << (p_layer_number - 1));
}
- set_collision_layer(collision_layer);
+ set_collision_layer(layer);
}
bool CSGShape3D::get_collision_layer_value(int p_layer_number) const {
@@ -690,7 +690,7 @@ CSGCombiner3D::CSGCombiner3D() {
/////////////////////
CSGBrush *CSGPrimitive3D::_create_brush_from_arrays(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uv, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials) {
- CSGBrush *brush = memnew(CSGBrush);
+ CSGBrush *new_brush = memnew(CSGBrush);
Vector<bool> invert;
invert.resize(p_vertices.size() / 3);
@@ -701,9 +701,9 @@ CSGBrush *CSGPrimitive3D::_create_brush_from_arrays(const Vector<Vector3> &p_ver
w[i] = flip_faces;
}
}
- brush->build_from_faces(p_vertices, p_uv, p_smooth, p_materials, invert);
+ new_brush->build_from_faces(p_vertices, p_uv, p_smooth, p_materials, invert);
- return brush;
+ return new_brush;
}
void CSGPrimitive3D::_bind_methods() {
@@ -742,7 +742,7 @@ CSGBrush *CSGMesh3D::_build_brush() {
Vector<bool> smooth;
Vector<Ref<Material>> materials;
Vector<Vector2> uvs;
- Ref<Material> material = get_material();
+ Ref<Material> base_material = get_material();
for (int i = 0; i < mesh->get_surface_count(); i++) {
if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
@@ -776,8 +776,8 @@ CSGBrush *CSGMesh3D::_build_brush() {
}
Ref<Material> mat;
- if (material.is_valid()) {
- mat = material;
+ if (base_material.is_valid()) {
+ mat = base_material;
} else {
mat = mesh->surface_get_material(i);
}
@@ -933,12 +933,12 @@ Ref<Mesh> CSGMesh3D::get_mesh() {
CSGBrush *CSGSphere3D::_build_brush() {
// set our bounding box
- CSGBrush *brush = memnew(CSGBrush);
+ CSGBrush *new_brush = memnew(CSGBrush);
int face_count = rings * radial_segments * 2 - radial_segments * 2;
bool invert_val = get_flip_faces();
- Ref<Material> material = get_material();
+ Ref<Material> base_material = get_material();
Vector<Vector3> faces;
Vector<Vector2> uvs;
@@ -1019,7 +1019,7 @@ CSGBrush *CSGSphere3D::_build_brush() {
smoothw[face] = smooth_faces;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
}
@@ -1036,7 +1036,7 @@ CSGBrush *CSGSphere3D::_build_brush() {
smoothw[face] = smooth_faces;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
}
@@ -1048,9 +1048,9 @@ CSGBrush *CSGSphere3D::_build_brush() {
}
}
- brush->build_from_faces(faces, uvs, smooth, materials, invert);
+ new_brush->build_from_faces(faces, uvs, smooth, materials, invert);
- return brush;
+ return new_brush;
}
void CSGSphere3D::_bind_methods() {
@@ -1137,12 +1137,12 @@ CSGSphere3D::CSGSphere3D() {
CSGBrush *CSGBox3D::_build_brush() {
// set our bounding box
- CSGBrush *brush = memnew(CSGBrush);
+ CSGBrush *new_brush = memnew(CSGBrush);
int face_count = 12; //it's a cube..
bool invert_val = get_flip_faces();
- Ref<Material> material = get_material();
+ Ref<Material> base_material = get_material();
Vector<Vector3> faces;
Vector<Vector2> uvs;
@@ -1204,7 +1204,7 @@ CSGBrush *CSGBox3D::_build_brush() {
smoothw[face] = false;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
//face 2
@@ -1218,7 +1218,7 @@ CSGBrush *CSGBox3D::_build_brush() {
smoothw[face] = false;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
}
@@ -1229,9 +1229,9 @@ CSGBrush *CSGBox3D::_build_brush() {
}
}
- brush->build_from_faces(faces, uvs, smooth, materials, invert);
+ new_brush->build_from_faces(faces, uvs, smooth, materials, invert);
- return brush;
+ return new_brush;
}
void CSGBox3D::_bind_methods() {
@@ -1270,12 +1270,12 @@ Ref<Material> CSGBox3D::get_material() const {
CSGBrush *CSGCylinder3D::_build_brush() {
// set our bounding box
- CSGBrush *brush = memnew(CSGBrush);
+ CSGBrush *new_brush = memnew(CSGBrush);
int face_count = sides * (cone ? 1 : 2) + sides + (cone ? 0 : sides);
bool invert_val = get_flip_faces();
- Ref<Material> material = get_material();
+ Ref<Material> base_material = get_material();
Vector<Vector3> faces;
Vector<Vector2> uvs;
@@ -1312,14 +1312,14 @@ CSGBrush *CSGCylinder3D::_build_brush() {
float ang = inc * Math_TAU;
float ang_n = inc_n * Math_TAU;
- Vector3 base(Math::cos(ang), 0, Math::sin(ang));
- Vector3 base_n(Math::cos(ang_n), 0, Math::sin(ang_n));
+ Vector3 face_base(Math::cos(ang), 0, Math::sin(ang));
+ Vector3 face_base_n(Math::cos(ang_n), 0, Math::sin(ang_n));
Vector3 face_points[4] = {
- base + Vector3(0, -1, 0),
- base_n + Vector3(0, -1, 0),
- base_n * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0),
- base * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0),
+ face_base + Vector3(0, -1, 0),
+ face_base_n + Vector3(0, -1, 0),
+ face_base_n * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0),
+ face_base * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0),
};
Vector2 u[4] = {
@@ -1340,7 +1340,7 @@ CSGBrush *CSGCylinder3D::_build_brush() {
smoothw[face] = smooth_faces;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
@@ -1356,7 +1356,7 @@ CSGBrush *CSGCylinder3D::_build_brush() {
smoothw[face] = smooth_faces;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
}
@@ -1371,7 +1371,7 @@ CSGBrush *CSGCylinder3D::_build_brush() {
smoothw[face] = false;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
if (!cone) {
@@ -1386,7 +1386,7 @@ CSGBrush *CSGCylinder3D::_build_brush() {
smoothw[face] = false;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
}
}
@@ -1397,9 +1397,9 @@ CSGBrush *CSGCylinder3D::_build_brush() {
}
}
- brush->build_from_faces(faces, uvs, smooth, materials, invert);
+ new_brush->build_from_faces(faces, uvs, smooth, materials, invert);
- return brush;
+ return new_brush;
}
void CSGCylinder3D::_bind_methods() {
@@ -1515,12 +1515,12 @@ CSGBrush *CSGTorus3D::_build_brush() {
float radius = (max_radius - min_radius) * 0.5;
- CSGBrush *brush = memnew(CSGBrush);
+ CSGBrush *new_brush = memnew(CSGBrush);
int face_count = ring_sides * sides * 2;
bool invert_val = get_flip_faces();
- Ref<Material> material = get_material();
+ Ref<Material> base_material = get_material();
Vector<Vector3> faces;
Vector<Vector2> uvs;
@@ -1596,7 +1596,7 @@ CSGBrush *CSGTorus3D::_build_brush() {
smoothw[face] = smooth_faces;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
@@ -1611,7 +1611,7 @@ CSGBrush *CSGTorus3D::_build_brush() {
smoothw[face] = smooth_faces;
invertw[face] = invert_val;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
}
}
@@ -1622,9 +1622,9 @@ CSGBrush *CSGTorus3D::_build_brush() {
}
}
- brush->build_from_faces(faces, uvs, smooth, materials, invert);
+ new_brush->build_from_faces(faces, uvs, smooth, materials, invert);
- return brush;
+ return new_brush;
}
void CSGTorus3D::_bind_methods() {
@@ -1726,10 +1726,10 @@ CSGTorus3D::CSGTorus3D() {
///////////////
CSGBrush *CSGPolygon3D::_build_brush() {
- CSGBrush *brush = memnew(CSGBrush);
+ CSGBrush *new_brush = memnew(CSGBrush);
if (polygon.size() < 3) {
- return brush;
+ return new_brush;
}
// Triangulate polygon shape.
@@ -1739,7 +1739,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
}
int shape_sides = shape_polygon.size();
Vector<int> shape_faces = Geometry2D::triangulate_polygon(shape_polygon);
- ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, brush, "Failed to triangulate CSGPolygon. Make sure the polygon doesn't have any intersecting edges.");
+ ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, new_brush, "Failed to triangulate CSGPolygon. Make sure the polygon doesn't have any intersecting edges.");
// Get polygon enclosing Rect2.
Rect2 shape_rect(shape_polygon[0], Vector2());
@@ -1764,12 +1764,12 @@ CSGBrush *CSGPolygon3D::_build_brush() {
}
if (!path) {
- return brush;
+ return new_brush;
}
curve = path->get_curve();
if (curve.is_null() || curve->get_point_count() < 2) {
- return brush;
+ return new_brush;
}
}
@@ -1806,7 +1806,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
int face_count = extrusions * extrusion_face_count + end_count * shape_face_count;
// Initialize variables used to create the mesh.
- Ref<Material> material = get_material();
+ Ref<Material> base_material = get_material();
Vector<Vector3> faces;
Vector<Vector2> uvs;
@@ -1896,7 +1896,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
}
smoothw[face] = false;
- materialsw[face] = material;
+ materialsw[face] = base_material;
invertw[face] = flip_faces;
face++;
}
@@ -2003,7 +2003,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
smoothw[face] = smooth_faces;
invertw[face] = flip_faces;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
@@ -2018,7 +2018,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
smoothw[face] = smooth_faces;
invertw[face] = flip_faces;
- materialsw[face] = material;
+ materialsw[face] = base_material;
face++;
}
@@ -2041,14 +2041,14 @@ CSGBrush *CSGPolygon3D::_build_brush() {
}
smoothw[face] = false;
- materialsw[face] = material;
+ materialsw[face] = base_material;
invertw[face] = flip_faces;
face++;
}
}
face_count -= faces_removed;
- ERR_FAIL_COND_V_MSG(face != face_count, brush, "Bug: Failed to create the CSGPolygon mesh correctly.");
+ ERR_FAIL_COND_V_MSG(face != face_count, new_brush, "Bug: Failed to create the CSGPolygon mesh correctly.");
}
if (faces_removed > 0) {
@@ -2059,9 +2059,9 @@ CSGBrush *CSGPolygon3D::_build_brush() {
invert.resize(face_count);
}
- brush->build_from_faces(faces, uvs, smooth, materials, invert);
+ new_brush->build_from_faces(faces, uvs, smooth, materials, invert);
- return brush;
+ return new_brush;
}
void CSGPolygon3D::_notification(int p_what) {
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index ba9b96db74..73771b3639 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -57,7 +57,7 @@ CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
}
String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
- CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
+ CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
if (Object::cast_to<CSGSphere3D>(cs)) {
return "Radius";
@@ -79,7 +79,7 @@ String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo,
}
Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
- CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
+ CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
if (Object::cast_to<CSGSphere3D>(cs)) {
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
@@ -105,7 +105,7 @@ Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo
}
void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
- CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
+ CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
Transform3D gt = cs->get_global_transform();
//gt.orthonormalize();
@@ -208,7 +208,7 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i
}
void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
- CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
+ CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
if (Object::cast_to<CSGSphere3D>(cs)) {
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
@@ -308,7 +308,7 @@ bool CSGShape3DGizmoPlugin::is_selectable_when_hidden() const {
void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->clear();
- CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node());
+ CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
Vector<Vector3> faces = cs->get_brush_faces();
diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp
index dfdd08c9f4..e7728f4aec 100644
--- a/modules/enet/enet_multiplayer_peer.cpp
+++ b/modules/enet/enet_multiplayer_peer.cpp
@@ -436,9 +436,9 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size
int packet_flags = 0;
int channel = SYSCH_RELIABLE;
- int transfer_channel = get_transfer_channel();
- if (transfer_channel > 0) {
- channel = SYSCH_MAX + transfer_channel - 1;
+ int tr_channel = get_transfer_channel();
+ if (tr_channel > 0) {
+ channel = SYSCH_MAX + tr_channel - 1;
} else {
switch (get_transfer_mode()) {
case TRANSFER_MODE_UNRELIABLE: {
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index d0b4632ebe..bc44479f93 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -507,7 +507,7 @@
<param index="3" name="extra_hints" type="String" default="&quot;&quot;" />
<description>
Export a numeric property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting.
- If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"no_slider"[/code] hint will hide the slider element of the editor widget.
+ If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget.
Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string.
See also [constant PROPERTY_HINT_RANGE].
[codeblock]
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 8b27307d0c..996d323a7f 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -256,7 +256,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
String word = str.substr(from + 1, to - from);
// Keywords need to be exceptions, except for keywords that represent a value.
- if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !keywords.has(word)) {
+ if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !reserved_keywords.has(word)) {
if (!is_symbol(str[to]) || str[to] == '"' || str[to] == '\'' || str[to] == ')' || str[to] == ']' || str[to] == '}') {
is_binary_op = true;
}
@@ -338,8 +338,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
col = global_function_color;
}
}
- } else if (keywords.has(word)) {
- col = keywords[word];
+ } else if (class_names.has(word)) {
+ col = class_names[word];
+ } else if (reserved_keywords.has(word)) {
+ col = reserved_keywords[word];
} else if (member_keywords.has(word)) {
col = member_keywords[word];
}
@@ -563,7 +565,8 @@ PackedStringArray GDScriptSyntaxHighlighter::_get_supported_languages() const {
}
void GDScriptSyntaxHighlighter::_update_cache() {
- keywords.clear();
+ class_names.clear();
+ reserved_keywords.clear();
member_keywords.clear();
global_functions.clear();
color_regions.clear();
@@ -580,7 +583,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> types;
ClassDB::get_class_list(&types);
for (const StringName &E : types) {
- keywords[E] = types_color;
+ class_names[E] = types_color;
}
/* User types. */
@@ -588,14 +591,14 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> global_classes;
ScriptServer::get_global_class_list(&global_classes);
for (const StringName &E : global_classes) {
- keywords[E] = usertype_color;
+ class_names[E] = usertype_color;
}
/* Autoloads. */
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
const ProjectSettings::AutoloadInfo &info = E.value;
if (info.is_singleton) {
- keywords[info.name] = usertype_color;
+ class_names[info.name] = usertype_color;
}
}
@@ -606,7 +609,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<String> core_types;
gdscript->get_core_type_words(&core_types);
for (const String &E : core_types) {
- keywords[StringName(E)] = basetype_color;
+ class_names[StringName(E)] = basetype_color;
}
/* Reserved words. */
@@ -616,9 +619,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
gdscript->get_reserved_words(&keyword_list);
for (const String &E : keyword_list) {
if (gdscript->is_control_flow_keyword(E)) {
- keywords[StringName(E)] = control_flow_keyword_color;
+ reserved_keywords[StringName(E)] = control_flow_keyword_color;
} else {
- keywords[StringName(E)] = keyword_color;
+ reserved_keywords[StringName(E)] = keyword_color;
}
}
@@ -653,23 +656,23 @@ void GDScriptSyntaxHighlighter::_update_cache() {
add_color_region(beg, end, string_color, end.is_empty());
}
- const Ref<Script> script = _get_edited_resource();
- if (script.is_valid()) {
+ const Ref<Script> scr = _get_edited_resource();
+ if (scr.is_valid()) {
/* Member types. */
const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");
- StringName instance_base = script->get_instance_base_type();
+ StringName instance_base = scr->get_instance_base_type();
if (instance_base != StringName()) {
List<PropertyInfo> plist;
ClassDB::get_property_list(instance_base, &plist);
for (const PropertyInfo &E : plist) {
- String name = E.name;
+ String prop_name = E.name;
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
continue;
}
- if (name.contains("/")) {
+ if (prop_name.contains("/")) {
continue;
}
- member_keywords[name] = member_variable_color;
+ member_keywords[prop_name] = member_variable_color;
}
List<String> clist;
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index 60b5b092d4..7c22eb30b1 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -47,7 +47,8 @@ private:
Vector<ColorRegion> color_regions;
HashMap<int, int> color_region_cache;
- HashMap<StringName, Color> keywords;
+ HashMap<StringName, Color> class_names;
+ HashMap<StringName, Color> reserved_keywords;
HashMap<StringName, Color> member_keywords;
HashSet<StringName> global_functions;
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 54cadf7df3..0a9dad04c7 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -111,9 +111,9 @@ GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) {
if (p_script->initializer) {
return p_script->initializer;
} else {
- GDScript *base = p_script->_base;
- if (base != nullptr) {
- return _super_constructor(base);
+ GDScript *base_src = p_script->_base;
+ if (base_src != nullptr) {
+ return _super_constructor(base_src);
} else {
return nullptr;
}
@@ -121,9 +121,9 @@ GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) {
}
void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) {
- GDScript *base = p_script->_base;
- if (base != nullptr) {
- _super_implicit_constructor(base, p_instance, r_error);
+ GDScript *base_src = p_script->_base;
+ if (base_src != nullptr) {
+ _super_implicit_constructor(base_src, p_instance, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
return;
}
@@ -151,7 +151,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
/* STEP 2, INITIALIZE AND CONSTRUCT */
{
- MutexLock lock(GDScriptLanguage::singleton->lock);
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.insert(instance->owner);
}
@@ -160,7 +160,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
{
- MutexLock lock(GDScriptLanguage::singleton->lock);
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.erase(p_owner);
}
ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
@@ -177,7 +177,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
{
- MutexLock lock(GDScriptLanguage::singleton->lock);
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.erase(p_owner);
}
ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
@@ -396,9 +396,9 @@ ScriptInstance *GDScript::instance_create(Object *p_this) {
if (top->native.is_valid()) {
if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) {
if (EngineDebugger::is_active()) {
- GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'");
+ GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'");
}
- ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instantiated in object of type '" + p_this->get_class() + "'" + ".");
+ ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be assigned to an object of type '" + p_this->get_class() + "'" + ".");
}
}
@@ -418,7 +418,7 @@ PlaceHolderScriptInstance *GDScript::placeholder_instance_create(Object *p_this)
}
bool GDScript::instance_has(const Object *p_this) const {
- MutexLock lock(GDScriptLanguage::singleton->lock);
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
return instances.has((Object *)p_this);
}
@@ -620,11 +620,11 @@ void GDScript::_update_doc() {
}
if (!is_enum) {
DocData::ConstantDoc constant_doc;
- String doc_description;
+ String const_description;
if (doc_constants.has(E.key)) {
- doc_description = doc_constants[E.key];
+ const_description = doc_constants[E.key];
}
- DocData::constant_doc_from_variant(constant_doc, E.key, E.value, doc_description);
+ DocData::constant_doc_from_variant(constant_doc, E.key, E.value, const_description);
doc.constants.push_back(constant_doc);
}
}
@@ -675,35 +675,35 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc
}
if (c->extends_used) {
- String path = "";
+ String ext_path = "";
if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) {
- path = c->extends_path;
- if (path.is_relative_path()) {
- String base = get_path();
- if (base.is_empty() || base.is_relative_path()) {
- ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data());
+ ext_path = c->extends_path;
+ if (ext_path.is_relative_path()) {
+ String base_path = get_path();
+ if (base_path.is_empty() || base_path.is_relative_path()) {
+ ERR_PRINT(("Could not resolve relative path for parent class: " + ext_path).utf8().get_data());
} else {
- path = base.get_base_dir().path_join(path);
+ ext_path = base_path.get_base_dir().path_join(ext_path);
}
}
} else if (c->extends.size() != 0) {
- const StringName &base = c->extends[0];
+ const StringName &base_class = c->extends[0];
- if (ScriptServer::is_global_class(base)) {
- path = ScriptServer::get_global_class_path(base);
+ if (ScriptServer::is_global_class(base_class)) {
+ ext_path = ScriptServer::get_global_class_path(base_class);
}
}
- if (!path.is_empty()) {
- if (path != get_path()) {
- Ref<GDScript> bf = ResourceLoader::load(path);
+ if (!ext_path.is_empty()) {
+ if (ext_path != get_path()) {
+ Ref<GDScript> bf = ResourceLoader::load(ext_path);
if (bf.is_valid()) {
base_cache = bf;
bf->inheriters_cache.insert(get_instance_id());
}
} else {
- ERR_PRINT(("Path extending itself in " + path).utf8().get_data());
+ ERR_PRINT(("Path extending itself in " + ext_path).utf8().get_data());
}
}
}
@@ -843,7 +843,7 @@ String GDScript::_get_debug_path() const {
Error GDScript::reload(bool p_keep_state) {
bool has_instances;
{
- MutexLock lock(GDScriptLanguage::singleton->lock);
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
has_instances = instances.size();
}
@@ -1187,7 +1187,7 @@ GDScript::GDScript() :
script_list(this) {
#ifdef DEBUG_ENABLED
{
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
GDScriptLanguage::get_singleton()->script_list.add(&script_list);
}
@@ -1258,7 +1258,7 @@ void GDScript::_init_rpc_methods_properties() {
GDScript::~GDScript() {
{
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
// Order matters since clearing the stack may already cause
@@ -1295,7 +1295,7 @@ GDScript::~GDScript() {
#ifdef DEBUG_ENABLED
{
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
GDScriptLanguage::get_singleton()->script_list.remove(&script_list);
}
@@ -1722,7 +1722,7 @@ GDScriptInstance::GDScriptInstance() {
}
GDScriptInstance::~GDScriptInstance() {
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
// Order matters since clearing the stack may already cause
@@ -1825,7 +1825,7 @@ void GDScriptLanguage::finish() {
void GDScriptLanguage::profiling_start() {
#ifdef DEBUG_ENABLED
- MutexLock lock(this->lock);
+ MutexLock lock(this->mutex);
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
@@ -1847,7 +1847,7 @@ void GDScriptLanguage::profiling_start() {
void GDScriptLanguage::profiling_stop() {
#ifdef DEBUG_ENABLED
- MutexLock lock(this->lock);
+ MutexLock lock(this->mutex);
profiling = false;
#endif
@@ -1857,7 +1857,7 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr,
int current = 0;
#ifdef DEBUG_ENABLED
- MutexLock lock(this->lock);
+ MutexLock lock(this->mutex);
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
@@ -1880,7 +1880,7 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_
int current = 0;
#ifdef DEBUG_ENABLED
- MutexLock lock(this->lock);
+ MutexLock lock(this->mutex);
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
@@ -1926,7 +1926,7 @@ void GDScriptLanguage::reload_all_scripts() {
print_verbose("GDScript: Reloading all scripts");
List<Ref<GDScript>> scripts;
{
- MutexLock lock(this->lock);
+ MutexLock lock(this->mutex);
SelfList<GDScript> *elem = script_list.first();
while (elem) {
@@ -1942,10 +1942,10 @@ void GDScriptLanguage::reload_all_scripts() {
scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order
- for (Ref<GDScript> &script : scripts) {
- print_verbose("GDScript: Reloading: " + script->get_path());
- script->load_source_code(script->get_path());
- script->reload(true);
+ for (Ref<GDScript> &scr : scripts) {
+ print_verbose("GDScript: Reloading: " + scr->get_path());
+ scr->load_source_code(scr->get_path());
+ scr->reload(true);
}
#endif
}
@@ -1955,7 +1955,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
List<Ref<GDScript>> scripts;
{
- MutexLock lock(this->lock);
+ MutexLock lock(this->mutex);
SelfList<GDScript> *elem = script_list.first();
while (elem) {
@@ -1974,21 +1974,21 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order
- for (Ref<GDScript> &script : scripts) {
- bool reload = script == p_script || to_reload.has(script->get_base());
+ for (Ref<GDScript> &scr : scripts) {
+ bool reload = scr == p_script || to_reload.has(scr->get_base());
if (!reload) {
continue;
}
- to_reload.insert(script, HashMap<ObjectID, List<Pair<StringName, Variant>>>());
+ to_reload.insert(scr, HashMap<ObjectID, List<Pair<StringName, Variant>>>());
if (!p_soft_reload) {
//save state and remove script from instances
- HashMap<ObjectID, List<Pair<StringName, Variant>>> &map = to_reload[script];
+ HashMap<ObjectID, List<Pair<StringName, Variant>>> &map = to_reload[scr];
- while (script->instances.front()) {
- Object *obj = script->instances.front()->get();
+ while (scr->instances.front()) {
+ Object *obj = scr->instances.front()->get();
//save instance info
List<Pair<StringName, Variant>> state;
if (obj->get_script_instance()) {
@@ -2001,8 +2001,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
//same thing for placeholders
#ifdef TOOLS_ENABLED
- while (script->placeholders.size()) {
- Object *obj = (*script->placeholders.begin())->get_owner();
+ while (scr->placeholders.size()) {
+ Object *obj = (*scr->placeholders.begin())->get_owner();
//save instance info
if (obj->get_script_instance()) {
@@ -2012,13 +2012,13 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
obj->set_script(Variant());
} else {
// no instance found. Let's remove it so we don't loop forever
- script->placeholders.erase(*script->placeholders.begin());
+ scr->placeholders.erase(*scr->placeholders.begin());
}
}
#endif
- for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : script->pending_reload_state) {
+ for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) {
map[F.key] = F.value; //pending to reload, use this one instead
}
}
@@ -2043,9 +2043,9 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
}
obj->set_script(scr);
- ScriptInstance *script_instance = obj->get_script_instance();
+ ScriptInstance *script_inst = obj->get_script_instance();
- if (!script_instance) {
+ if (!script_inst) {
//failed, save reload state for next time if not saved
if (!scr->pending_reload_state.has(obj->get_instance_id())) {
scr->pending_reload_state[obj->get_instance_id()] = saved_state;
@@ -2053,14 +2053,14 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
continue;
}
- if (script_instance->is_placeholder() && scr->is_placeholder_fallback_enabled()) {
- PlaceHolderScriptInstance *placeholder = static_cast<PlaceHolderScriptInstance *>(script_instance);
+ if (script_inst->is_placeholder() && scr->is_placeholder_fallback_enabled()) {
+ PlaceHolderScriptInstance *placeholder = static_cast<PlaceHolderScriptInstance *>(script_inst);
for (List<Pair<StringName, Variant>>::Element *G = saved_state.front(); G; G = G->next()) {
placeholder->property_set_fallback(G->get().first, G->get().second);
}
} else {
for (List<Pair<StringName, Variant>>::Element *G = saved_state.front(); G; G = G->next()) {
- script_instance->set(G->get().first, G->get().second);
+ script_inst->set(G->get().first, G->get().second);
}
}
@@ -2078,7 +2078,7 @@ void GDScriptLanguage::frame() {
#ifdef DEBUG_ENABLED
if (profiling) {
- MutexLock lock(this->lock);
+ MutexLock lock(this->mutex);
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
@@ -2339,26 +2339,26 @@ GDScriptLanguage::~GDScriptLanguage() {
// Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit).
SelfList<GDScript> *s = script_list.first();
while (s) {
- GDScript *script = s->self();
+ GDScript *scr = s->self();
// This ensures the current script is not released before we can check what's the next one
// in the list (we can't get the next upfront because we don't know if the reference breaking
// will cause it -or any other after it, for that matter- to be released so the next one
// is not the same as before).
- script->reference();
+ scr->reference();
- for (KeyValue<StringName, GDScriptFunction *> &E : script->member_functions) {
+ for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) {
GDScriptFunction *func = E.value;
for (int i = 0; i < func->argument_types.size(); i++) {
func->argument_types.write[i].script_type_ref = Ref<Script>();
}
func->return_type.script_type_ref = Ref<Script>();
}
- for (KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) {
+ for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) {
E.value.data_type.script_type_ref = Ref<Script>();
}
s = s->next();
- script->unreference();
+ scr->unreference();
}
singleton = nullptr;
@@ -2390,20 +2390,20 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str
}
Error err;
- Ref<GDScript> script = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE);
+ Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE);
// TODO: Reintroduce binary and encrypted scripts.
- if (script.is_null()) {
+ if (scr.is_null()) {
// Don't fail loading because of parsing error.
- script.instantiate();
+ scr.instantiate();
}
if (r_error) {
*r_error = OK;
}
- return script;
+ return scr;
}
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index e4b12d4ddb..0a010e5ad0 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -342,7 +342,7 @@ class GDScriptLanguage : public ScriptLanguage {
friend class GDScriptInstance;
- Mutex lock;
+ Mutex mutex;
friend class GDScript;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 32d9aec84f..e1beb2f374 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -262,19 +262,19 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
if (p_class->extends_path.is_relative_path()) {
p_class->extends_path = class_type.script_path.get_base_dir().path_join(p_class->extends_path).simplify_path();
}
- Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path);
- if (parser.is_null()) {
+ Ref<GDScriptParserRef> ext_parser = get_parser_for(p_class->extends_path);
+ if (ext_parser.is_null()) {
push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class);
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ Error err = ext_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class);
return err;
}
- base = parser->get_parser()->head->get_datatype();
+ base = ext_parser->get_parser()->head->get_datatype();
} else {
if (p_class->extends.is_empty()) {
push_error("Could not resolve an empty super class path.", p_class);
@@ -289,18 +289,18 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
if (base_path == parser->script_path) {
base = parser->head->get_datatype();
} else {
- Ref<GDScriptParserRef> parser = get_parser_for(base_path);
- if (parser.is_null()) {
+ Ref<GDScriptParserRef> base_parser = get_parser_for(base_path);
+ if (base_parser.is_null()) {
push_error(vformat(R"(Could not resolve super class "%s".)", name), p_class);
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ Error err = base_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err;
}
- base = parser->get_parser()->head->get_datatype();
+ base = base_parser->get_parser()->head->get_datatype();
}
} else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name);
@@ -309,13 +309,13 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
return ERR_PARSE_ERROR;
}
- Ref<GDScriptParserRef> parser = get_parser_for(info.path);
- if (parser.is_null()) {
+ Ref<GDScriptParserRef> info_parser = get_parser_for(info.path);
+ if (info_parser.is_null()) {
push_error(vformat(R"(Could not parse singleton from "%s".)", info.path), p_class);
return ERR_PARSE_ERROR;
}
- Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ Error err = info_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err;
@@ -3093,11 +3093,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
result.kind = GDScriptParser::DataType::NATIVE;
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) {
- Ref<GDScriptParserRef> parser = get_parser_for(autoload.path);
- if (parser.is_valid()) {
- Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path);
+ if (singl_parser.is_valid()) {
+ Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
if (err == OK) {
- result = type_from_metatype(parser->get_parser()->head->get_datatype());
+ result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
}
}
}
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index fd418ced47..7acd1cdb96 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -522,11 +522,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Create temporary for result first since it will be deleted last.
GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type);
- GDScriptCodeGenerator::Address source = _parse_expression(codegen, r_error, cn->operand);
+ GDScriptCodeGenerator::Address src = _parse_expression(codegen, r_error, cn->operand);
- gen->write_cast(result, source, cast_type);
+ gen->write_cast(result, src, cast_type);
- if (source.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ if (src.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
gen->pop_temporary();
}
@@ -1650,7 +1650,7 @@ void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptPars
}
Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals) {
- Error error = OK;
+ Error err = OK;
GDScriptCodeGenerator *gen = codegen.generator;
codegen.start_block();
@@ -1676,9 +1676,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
// Evaluate the match expression.
GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype()));
- GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, error, match->test);
- if (error) {
- return error;
+ GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, err, match->test);
+ if (err) {
+ return err;
}
// Assign to local.
@@ -1723,9 +1723,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
// For each pattern in branch.
GDScriptCodeGenerator::Address pattern_result = codegen.add_temporary();
for (int k = 0; k < branch->patterns.size(); k++) {
- pattern_result = _parse_match_pattern(codegen, error, branch->patterns[k], value, type, pattern_result, k == 0, false);
- if (error != OK) {
- return error;
+ pattern_result = _parse_match_pattern(codegen, err, branch->patterns[k], value, type, pattern_result, k == 0, false);
+ if (err != OK) {
+ return err;
}
}
@@ -1736,9 +1736,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
gen->pop_temporary();
// Parse the branch block.
- error = _parse_block(codegen, branch->block, false); // Don't add locals again.
- if (error) {
- return error;
+ err = _parse_block(codegen, branch->block, false); // Don't add locals again.
+ if (err) {
+ return err;
}
codegen.end_block(); // Get out of extra block.
@@ -1753,9 +1753,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
} break;
case GDScriptParser::Node::IF: {
const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s);
- GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, if_n->condition);
- if (error) {
- return error;
+ GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, if_n->condition);
+ if (err) {
+ return err;
}
gen->write_if(condition);
@@ -1764,17 +1764,17 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.generator->pop_temporary();
}
- error = _parse_block(codegen, if_n->true_block);
- if (error) {
- return error;
+ err = _parse_block(codegen, if_n->true_block);
+ if (err) {
+ return err;
}
if (if_n->false_block) {
gen->write_else();
- error = _parse_block(codegen, if_n->false_block);
- if (error) {
- return error;
+ err = _parse_block(codegen, if_n->false_block);
+ if (err) {
+ return err;
}
}
@@ -1788,9 +1788,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype()));
- GDScriptCodeGenerator::Address list = _parse_expression(codegen, error, for_n->list);
- if (error) {
- return error;
+ GDScriptCodeGenerator::Address list = _parse_expression(codegen, err, for_n->list);
+ if (err) {
+ return err;
}
gen->write_for_assignment(iterator, list);
@@ -1801,9 +1801,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
gen->write_for();
- error = _parse_block(codegen, for_n->loop);
- if (error) {
- return error;
+ err = _parse_block(codegen, for_n->loop);
+ if (err) {
+ return err;
}
gen->write_endfor();
@@ -1815,9 +1815,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
gen->start_while_condition();
- GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, while_n->condition);
- if (error) {
- return error;
+ GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, while_n->condition);
+ if (err) {
+ return err;
}
gen->write_while(condition);
@@ -1826,9 +1826,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.generator->pop_temporary();
}
- error = _parse_block(codegen, while_n->loop);
- if (error) {
- return error;
+ err = _parse_block(codegen, while_n->loop);
+ if (err) {
+ return err;
}
gen->write_endwhile();
@@ -1850,9 +1850,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
GDScriptCodeGenerator::Address return_value;
if (return_n->return_value != nullptr) {
- return_value = _parse_expression(codegen, error, return_n->return_value);
- if (error) {
- return error;
+ return_value = _parse_expression(codegen, err, return_n->return_value);
+ if (err) {
+ return err;
}
}
@@ -1865,17 +1865,17 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
#ifdef DEBUG_ENABLED
const GDScriptParser::AssertNode *as = static_cast<const GDScriptParser::AssertNode *>(s);
- GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, as->condition);
- if (error) {
- return error;
+ GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, as->condition);
+ if (err) {
+ return err;
}
GDScriptCodeGenerator::Address message;
if (as->message) {
- message = _parse_expression(codegen, error, as->message);
- if (error) {
- return error;
+ message = _parse_expression(codegen, err, as->message);
+ if (err) {
+ return err;
}
}
gen->write_assert(condition, message);
@@ -1908,9 +1908,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>());
}
}
- GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, lv->initializer);
- if (error) {
- return error;
+ GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, lv->initializer);
+ if (err) {
+ return err;
}
if (lv->use_conversion_assign) {
gen->write_assign_with_conversion(local, src_address);
@@ -1946,9 +1946,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
default: {
// Expression.
if (s->is_expression()) {
- GDScriptCodeGenerator::Address expr = _parse_expression(codegen, error, static_cast<const GDScriptParser::ExpressionNode *>(s), true);
- if (error) {
- return error;
+ GDScriptCodeGenerator::Address expr = _parse_expression(codegen, err, static_cast<const GDScriptParser::ExpressionNode *>(s), true);
+ if (err) {
+ return err;
}
if (expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
@@ -2180,7 +2180,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
}
Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) {
- Error error = OK;
+ Error err = OK;
GDScriptParser::FunctionNode *function;
@@ -2190,9 +2190,9 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
function = p_variable->getter;
}
- _parse_function(error, p_script, p_class, function);
+ _parse_function(err, p_script, p_class, function);
- return error;
+ return err;
}
Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) {
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 4841884e2d..e4264ea55b 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -81,10 +81,10 @@ class GDScriptCompiler {
type.kind = GDScriptDataType::NATIVE;
type.native_type = obj->get_class_name();
- Ref<Script> script = obj->get_script();
- if (script.is_valid()) {
- type.script_type = script.ptr();
- Ref<GDScript> gdscript = script;
+ Ref<Script> scr = obj->get_script();
+ if (scr.is_valid()) {
+ type.script_type = scr.ptr();
+ Ref<GDScript> gdscript = scr;
if (gdscript.is_valid()) {
type.kind = GDScriptDataType::GDSCRIPT;
} else {
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index b38c7c6699..b5a209c805 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -104,10 +104,10 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += ": ";
// This makes the compiler complain if some opcode is unchecked in the switch.
- Opcode code = Opcode(_code_ptr[ip] & INSTR_MASK);
+ Opcode opcode = Opcode(_code_ptr[ip] & INSTR_MASK);
int instr_var_args = (_code_ptr[ip] & INSTR_ARGS_MASK) >> INSTR_BITS;
- switch (code) {
+ switch (opcode) {
case OPCODE_OPERATOR: {
int operation = _code_ptr[ip + 4];
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index c00036c9f0..3c68993b36 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -61,8 +61,8 @@ bool GDScriptLanguage::is_using_templates() {
}
Ref<Script> GDScriptLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const {
- Ref<GDScript> script;
- script.instantiate();
+ Ref<GDScript> scr;
+ scr.instantiate();
String processed_template = p_template;
bool type_hints = false;
#ifdef TOOLS_ENABLED
@@ -82,8 +82,8 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri
processed_template = processed_template.replace("_BASE_", p_base_class_name)
.replace("_CLASS_", p_class_name)
.replace("_TS_", _get_indentation());
- script->set_source_code(processed_template);
- return script;
+ scr->set_source_code(processed_template);
+ return scr;
}
Vector<ScriptLanguage::ScriptTemplate> GDScriptLanguage::get_built_in_templates(StringName p_object) {
@@ -318,10 +318,10 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *
return;
}
- Ref<GDScript> script = instance->get_script();
- ERR_FAIL_COND(script.is_null());
+ Ref<GDScript> scr = instance->get_script();
+ ERR_FAIL_COND(scr.is_null());
- const HashMap<StringName, GDScript::MemberInfo> &mi = script->debug_get_member_indices();
+ const HashMap<StringName, GDScript::MemberInfo> &mi = scr->debug_get_member_indices();
for (const KeyValue<StringName, GDScript::MemberInfo> &E : mi) {
p_members->push_back(E.key);
@@ -344,7 +344,7 @@ ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) {
void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
const HashMap<StringName, int> &name_idx = GDScriptLanguage::get_singleton()->get_global_map();
- const Variant *globals = GDScriptLanguage::get_singleton()->get_global_array();
+ const Variant *gl_array = GDScriptLanguage::get_singleton()->get_global_array();
List<Pair<String, Variant>> cinfo;
get_public_constants(&cinfo);
@@ -365,7 +365,7 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant>
continue;
}
- const Variant &var = globals[E.value];
+ const Variant &var = gl_array[E.value];
if (Object *obj = var) {
if (Object::cast_to<GDScriptNativeClass>(obj)) {
continue;
@@ -766,7 +766,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
ScriptLanguage::CodeCompletionOption slider2("or_less", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
slider2.insert_text = slider2.display.quote(p_quote_style);
r_result.insert(slider2.display, slider2);
- ScriptLanguage::CodeCompletionOption slider3("no_slider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ ScriptLanguage::CodeCompletionOption slider3("hide_slider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
slider3.insert_text = slider3.display.quote(p_quote_style);
r_result.insert(slider3.display, slider3);
}
@@ -1205,8 +1205,8 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
_find_built_in_variants(r_result);
static const char *_keywords[] = {
- "false", "PI", "TAU", "INF", "NAN", "self", "true", "breakpoint", "tool", "super",
- "break", "continue", "pass", "return",
+ "true", "false", "PI", "TAU", "INF", "NAN", "null", "self", "super",
+ "break", "breakpoint", "continue", "pass", "return",
nullptr
};
@@ -1218,7 +1218,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
}
static const char *_keywords_with_space[] = {
- "and", "in", "not", "or", "as", "class", "extends", "is", "func", "signal", "await",
+ "and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await",
"const", "enum", "static", "var", "if", "elif", "else", "for", "match", "while",
nullptr
};
@@ -3302,17 +3302,17 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) {
const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(p_symbol);
if (autoload.is_singleton) {
- String script = autoload.path;
- if (!script.ends_with(".gd")) {
+ String scr_path = autoload.path;
+ if (!scr_path.ends_with(".gd")) {
// Not a script, try find the script anyway,
// may have some success.
- script = script.get_basename() + ".gd";
+ scr_path = scr_path.get_basename() + ".gd";
}
- if (FileAccess::exists(script)) {
+ if (FileAccess::exists(scr_path)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
r_result.location = 0;
- r_result.script = ResourceLoader::load(script);
+ r_result.script = ResourceLoader::load(scr_path);
return OK;
}
}
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index cd3b7d69c5..98b3e40f1b 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -142,7 +142,7 @@ GDScriptFunction::GDScriptFunction() {
name = "<anonymous>";
#ifdef DEBUG_ENABLED
{
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
GDScriptLanguage::get_singleton()->function_list.add(&function_list);
}
#endif
@@ -155,7 +155,7 @@ GDScriptFunction::~GDScriptFunction() {
#ifdef DEBUG_ENABLED
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
GDScriptLanguage::get_singleton()->function_list.remove(&function_list);
#endif
@@ -201,7 +201,7 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
}
if (p_extended_check) {
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
// Script gone?
if (!scripts_list.in_list()) {
@@ -219,7 +219,7 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
Variant GDScriptFunctionState::resume(const Variant &p_arg) {
ERR_FAIL_COND_V(!function, Variant());
{
- MutexLock lock(GDScriptLanguage::singleton->lock);
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
if (!scripts_list.in_list()) {
#ifdef DEBUG_ENABLED
@@ -304,7 +304,7 @@ GDScriptFunctionState::GDScriptFunctionState() :
GDScriptFunctionState::~GDScriptFunctionState() {
{
- MutexLock lock(GDScriptLanguage::singleton->lock);
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
scripts_list.remove_from_list();
instances_list.remove_from_list();
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 980a946e23..bdf6fb35b6 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3953,28 +3953,22 @@ GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const
}
String GDScriptParser::SuiteNode::Local::get_name() const {
- String name;
switch (type) {
case SuiteNode::Local::PARAMETER:
- name = "parameter";
- break;
+ return "parameter";
case SuiteNode::Local::CONSTANT:
- name = "constant";
- break;
+ return "constant";
case SuiteNode::Local::VARIABLE:
- name = "variable";
- break;
+ return "variable";
case SuiteNode::Local::FOR_VARIABLE:
- name = "for loop iterator";
- break;
+ return "for loop iterator";
case SuiteNode::Local::PATTERN_BIND:
- name = "pattern_bind";
- break;
+ return "pattern_bind";
case SuiteNode::Local::UNDEFINED:
- name = "<undefined>";
- break;
+ return "<undefined>";
+ default:
+ return String();
}
- return name;
}
String GDScriptParser::DataType::to_string() const {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index d4efab173b..1850a44678 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -578,19 +578,19 @@ public:
return m_enum->get_datatype();
case ENUM_VALUE: {
// Always integer.
- DataType type;
- type.type_source = DataType::ANNOTATED_EXPLICIT;
- type.kind = DataType::BUILTIN;
- type.builtin_type = Variant::INT;
- return type;
+ DataType out_type;
+ out_type.type_source = DataType::ANNOTATED_EXPLICIT;
+ out_type.kind = DataType::BUILTIN;
+ out_type.builtin_type = Variant::INT;
+ return out_type;
}
case SIGNAL: {
- DataType type;
- type.type_source = DataType::ANNOTATED_EXPLICIT;
- type.kind = DataType::BUILTIN;
- type.builtin_type = Variant::SIGNAL;
+ DataType out_type;
+ out_type.type_source = DataType::ANNOTATED_EXPLICIT;
+ out_type.kind = DataType::BUILTIN;
+ out_type.builtin_type = Variant::SIGNAL;
// TODO: Add parameter info.
- return type;
+ return out_type;
}
case GROUP: {
return DataType();
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 6c17afe939..9bbfd7aece 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -516,15 +516,15 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
_advance();
}
- int length = _current - _start;
+ int len = _current - _start;
- if (length == 1 && _peek(-1) == '_') {
+ if (len == 1 && _peek(-1) == '_') {
// Lone underscore.
return make_token(Token::UNDERSCORE);
}
- String name(_start, length);
- if (length < MIN_KEYWORD_LENGTH || length > MAX_KEYWORD_LENGTH) {
+ String name(_start, len);
+ if (len < MIN_KEYWORD_LENGTH || len > MAX_KEYWORD_LENGTH) {
// Cannot be a keyword, as the length doesn't match any.
return make_identifier(name);
}
@@ -538,7 +538,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
const int keyword_length = sizeof(keyword) - 1; \
static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length"); \
static_assert(keyword_length >= MIN_KEYWORD_LENGTH, "There's a keyword shorter than the defined minimum length"); \
- if (keyword_length == length && name == keyword) { \
+ if (keyword_length == len && name == keyword) { \
return make_token(token_type); \
} \
}
@@ -551,13 +551,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
}
// Check if it's a special literal
- if (length == 4) {
+ if (len == 4) {
if (name == "true") {
return make_literal(true);
} else if (name == "null") {
return make_literal(Variant());
}
- } else if (length == 5) {
+ } else if (len == 5) {
if (name == "false") {
return make_literal(false);
}
@@ -725,8 +725,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
}
// Create a string with the whole number.
- int length = _current - _start;
- String number = String(_start, length).replace("_", "");
+ int len = _current - _start;
+ String number = String(_start, len).replace("_", "");
// Convert to the appropriate literal type.
if (base == 16) {
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index afebe3c149..c73ba798aa 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -2216,7 +2216,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
gdfs->state.line = line;
gdfs->state.script = _script;
{
- MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
_script->pending_func_states.add(&gdfs->scripts_list);
if (p_instance) {
gdfs->state.instance = p_instance;
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index 46a9b33eb0..de3becbaf8 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -38,8 +38,8 @@
void ExtendGDScriptParser::update_diagnostics() {
diagnostics.clear();
- const List<ParserError> &errors = get_errors();
- for (const ParserError &error : errors) {
+ const List<ParserError> &parser_errors = get_errors();
+ for (const ParserError &error : parser_errors) {
lsp::Diagnostic diagnostic;
diagnostic.severity = lsp::DiagnosticSeverity::Error;
diagnostic.message = error.message;
@@ -47,9 +47,9 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.code = -1;
lsp::Range range;
lsp::Position pos;
- const PackedStringArray lines = get_lines();
- int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, lines.size() - 1);
- const String &line_text = lines[line];
+ const PackedStringArray line_array = get_lines();
+ int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, line_array.size() - 1);
+ const String &line_text = line_array[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
range.start = pos;
@@ -59,8 +59,8 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostics.push_back(diagnostic);
}
- const List<GDScriptWarning> &warnings = get_warnings();
- for (const GDScriptWarning &warning : warnings) {
+ const List<GDScriptWarning> &parser_warnings = get_warnings();
+ for (const GDScriptWarning &warning : parser_warnings) {
lsp::Diagnostic diagnostic;
diagnostic.severity = lsp::DiagnosticSeverity::Warning;
diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message();
@@ -83,8 +83,7 @@ void ExtendGDScriptParser::update_diagnostics() {
void ExtendGDScriptParser::update_symbols() {
members.clear();
- const GDScriptParser::Node *head = get_tree();
- if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
+ if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(get_tree())) {
parse_class_symbol(gdclass, class_symbol);
for (int i = 0; i < class_symbol.children.size(); i++) {
@@ -107,26 +106,26 @@ void ExtendGDScriptParser::update_symbols() {
void ExtendGDScriptParser::update_document_links(const String &p_code) {
document_links.clear();
- GDScriptTokenizer tokenizer;
+ GDScriptTokenizer scr_tokenizer;
Ref<FileAccess> fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);
- tokenizer.set_source_code(p_code);
+ scr_tokenizer.set_source_code(p_code);
while (true) {
- GDScriptTokenizer::Token token = tokenizer.scan();
+ GDScriptTokenizer::Token token = scr_tokenizer.scan();
if (token.type == GDScriptTokenizer::Token::TK_EOF) {
break;
} else if (token.type == GDScriptTokenizer::Token::LITERAL) {
const Variant &const_val = token.literal;
if (const_val.get_type() == Variant::STRING) {
- String path = const_val;
- bool exists = fs->file_exists(path);
+ String scr_path = const_val;
+ bool exists = fs->file_exists(scr_path);
if (!exists) {
- path = get_path().get_base_dir() + "/" + path;
- exists = fs->file_exists(path);
+ scr_path = get_path().get_base_dir() + "/" + scr_path;
+ exists = fs->file_exists(scr_path);
}
if (exists) {
String value = const_val;
lsp::DocumentLink link;
- link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
+ link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(scr_path);
link.range.start.line = LINE_NUMBER_TO_INDEX(token.start_line);
link.range.end.line = LINE_NUMBER_TO_INDEX(token.end_line);
link.range.start.character = LINE_NUMBER_TO_INDEX(token.start_column);
@@ -437,11 +436,11 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {
if (!p_docs_down) { // inline comment
String inline_comment = lines[p_line];
- int comment_start = inline_comment.find("#");
+ int comment_start = inline_comment.find("##");
if (comment_start != -1) {
inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges();
if (inline_comment.length() > 1) {
- doc_lines.push_back(inline_comment.substr(1, inline_comment.length()));
+ doc_lines.push_back(inline_comment.substr(2, inline_comment.length()));
}
}
}
@@ -454,8 +453,8 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {
}
String line_comment = lines[i].strip_edges(true, false);
- if (line_comment.begins_with("#")) {
- line_comment = line_comment.substr(1, line_comment.length());
+ if (line_comment.begins_with("##")) {
+ line_comment = line_comment.substr(2, line_comment.length());
if (p_docs_down) {
doc_lines.push_back(line_comment);
} else {
@@ -731,7 +730,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
Array nested_classes;
Array constants;
- Array members;
+ Array class_members;
Array signals;
Array methods;
Array static_functions;
@@ -792,7 +791,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
api["signature"] = symbol->detail;
api["description"] = symbol->documentation;
}
- members.push_back(api);
+ class_members.push_back(api);
} break;
case ClassNode::Member::SIGNAL: {
Dictionary api;
@@ -824,7 +823,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
class_api["sub_classes"] = nested_classes;
class_api["constants"] = constants;
- class_api["members"] = members;
+ class_api["members"] = class_members;
class_api["signals"] = signals;
class_api["methods"] = methods;
class_api["static_functions"] = static_functions;
@@ -834,8 +833,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
Dictionary ExtendGDScriptParser::generate_api() const {
Dictionary api;
- const GDScriptParser::Node *head = get_tree();
- if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
+ if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(get_tree())) {
api = dump_class_api(gdclass);
}
return api;
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index ead4ef1987..38bea314a0 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -61,12 +61,12 @@ void GDScriptLanguageServer::_notification(int p_what) {
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- String host = String(_EDITOR_GET("network/language_server/remote_host"));
- int port = (int)_EDITOR_GET("network/language_server/remote_port");
- bool use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
- if (host != this->host || port != this->port || use_thread != this->use_thread) {
- this->stop();
- this->start();
+ String remote_host = String(_EDITOR_GET("network/language_server/remote_host"));
+ int remote_port = (int)_EDITOR_GET("network/language_server/remote_port");
+ bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
+ if (remote_host != host || remote_port != port || remote_use_thread != use_thread) {
+ stop();
+ start();
}
} break;
}
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index ccde0521f2..3905e28bcb 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -42,6 +42,7 @@ void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose);
ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
+ ClassDB::bind_method(D_METHOD("willSaveWaitUntil"), &GDScriptTextDocument::willSaveWaitUntil);
ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave);
ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol);
ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);
@@ -81,6 +82,16 @@ void GDScriptTextDocument::didChange(const Variant &p_param) {
sync_script_content(doc.uri, doc.text);
}
+void GDScriptTextDocument::willSaveWaitUntil(const Variant &p_param) {
+ lsp::TextDocumentItem doc = load_document_item(p_param);
+
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
+ Ref<Script> scr = ResourceLoader::load(path);
+ if (scr.is_valid()) {
+ ScriptEditor::get_singleton()->clear_docs_from_script(scr);
+ }
+}
+
void GDScriptTextDocument::didSave(const Variant &p_param) {
lsp::TextDocumentItem doc = load_document_item(p_param);
Dictionary dict = p_param;
@@ -88,11 +99,16 @@ void GDScriptTextDocument::didSave(const Variant &p_param) {
sync_script_content(doc.uri, text);
- /*String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
-
- Ref<GDScript> script = ResourceLoader::load(path);
- script->load_source_code(path);
- script->reload(true);*/
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
+ Ref<GDScript> scr = ResourceLoader::load(path);
+ if (scr.is_valid() && (scr->load_source_code(path) == OK)) {
+ if (scr->is_tool()) {
+ scr->get_language()->reload_tool_script(scr, true);
+ } else {
+ scr->reload(true);
+ }
+ ScriptEditor::get_singleton()->update_docs_from_script(scr);
+ }
}
lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {
@@ -213,8 +229,8 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) {
arr = native_member_completions.duplicate();
for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) {
- ExtendGDScriptParser *script = E.value;
- const Array &items = script->get_member_completions();
+ ExtendGDScriptParser *scr = E.value;
+ const Array &items = scr->get_member_completions();
const int start_size = arr.size();
arr.resize(start_size + items.size());
@@ -417,14 +433,6 @@ void GDScriptTextDocument::sync_script_content(const String &p_path, const Strin
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
EditorFileSystem::get_singleton()->update_file(path);
- Error error;
- Ref<GDScript> script = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &error);
- if (error == OK) {
- if (script->load_source_code(path) == OK) {
- script->reload(true);
- ScriptEditor::get_singleton()->reload_scripts(true); // Refresh scripts opened in the internal editor.
- }
- }
}
void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h
index 87bc08a34e..456c7d71fe 100644
--- a/modules/gdscript/language_server/gdscript_text_document.h
+++ b/modules/gdscript/language_server/gdscript_text_document.h
@@ -45,6 +45,7 @@ protected:
void didOpen(const Variant &p_param);
void didClose(const Variant &p_param);
void didChange(const Variant &p_param);
+ void willSaveWaitUntil(const Variant &p_param);
void didSave(const Variant &p_param);
void sync_script_content(const String &p_path, const String &p_content);
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 16461b0a6c..390460bed9 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -55,14 +55,14 @@ void GDScriptWorkspace::_bind_methods() {
}
void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) {
- Ref<Script> script = obj->get_script();
+ Ref<Script> scr = obj->get_script();
- if (script->get_language()->get_name() != "GDScript") {
+ if (scr->get_language()->get_name() != "GDScript") {
return;
}
String function_signature = "func " + function;
- String source = script->get_source_code();
+ String source = scr->get_source_code();
if (source.contains(function_signature)) {
return;
@@ -98,7 +98,7 @@ void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStr
text_edit.newText = function_body;
- String uri = get_file_uri(script->get_path());
+ String uri = get_file_uri(scr->get_path());
lsp::ApplyWorkspaceEditParams params;
params.edit.add_edit(uri, text_edit);
@@ -118,12 +118,12 @@ void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) {
void GDScriptWorkspace::remove_cache_parser(const String &p_path) {
HashMap<String, ExtendGDScriptParser *>::Iterator parser = parse_results.find(p_path);
- HashMap<String, ExtendGDScriptParser *>::Iterator script = scripts.find(p_path);
- if (parser && script) {
- if (script->value && script->value == parser->value) {
- memdelete(script->value);
+ HashMap<String, ExtendGDScriptParser *>::Iterator scr = scripts.find(p_path);
+ if (parser && scr) {
+ if (scr->value && scr->value == parser->value) {
+ memdelete(scr->value);
} else {
- memdelete(script->value);
+ memdelete(scr->value);
memdelete(parser->value);
}
parse_results.erase(p_path);
@@ -131,8 +131,8 @@ void GDScriptWorkspace::remove_cache_parser(const String &p_path) {
} else if (parser) {
memdelete(parser->value);
parse_results.erase(p_path);
- } else if (script) {
- memdelete(script->value);
+ } else if (scr) {
+ memdelete(scr->value);
scripts.erase(p_path);
}
}
@@ -587,8 +587,8 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S
while (!stack.is_empty()) {
current = Object::cast_to<Node>(stack.pop_back());
- Ref<GDScript> script = current->get_script();
- if (script.is_valid() && script->get_path() == path) {
+ Ref<GDScript> scr = current->get_script();
+ if (scr.is_valid() && scr->get_path() == path) {
break;
}
for (int i = 0; i < current->get_child_count(); ++i) {
@@ -596,8 +596,8 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S
}
}
- Ref<GDScript> script = current->get_script();
- if (!script.is_valid() || script->get_path() != path) {
+ Ref<GDScript> scr = current->get_script();
+ if (!scr.is_valid() || scr->get_path() != path) {
current = owner_scene_node;
}
}
@@ -691,13 +691,13 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP
}
for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {
- const ExtendGDScriptParser *script = E.value;
- const ClassMembers &members = script->get_members();
+ const ExtendGDScriptParser *scr = E.value;
+ const ClassMembers &members = scr->get_members();
if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
r_list.push_back(*symbol);
}
- for (const KeyValue<String, ClassMembers> &F : script->get_inner_classes()) {
+ for (const KeyValue<String, ClassMembers> &F : scr->get_inner_classes()) {
const ClassMembers *inner_class = &F.value;
if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) {
r_list.push_back(*symbol);
diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h
index fbd40796c4..024da1cab7 100644
--- a/modules/gdscript/language_server/godot_lsp.h
+++ b/modules/gdscript/language_server/godot_lsp.h
@@ -546,7 +546,7 @@ struct TextDocumentSyncOptions {
* If present will save wait until requests are sent to the server. If omitted the request should not be
* sent.
*/
- bool willSaveWaitUntil = false;
+ bool willSaveWaitUntil = true;
/**
* If present save notifications are sent to the server. If omitted the notification should not be
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 6c346acb7e..15131afde7 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -479,9 +479,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
result.output = get_text_for_status(result.status) + "\n";
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
- for (const GDScriptParser::ParserError &E : errors) {
- result.output += E.message + "\n"; // TODO: line, column?
- break; // Only the first error since the following might be cascading.
+ if (!errors.is_empty()) {
+ // Only the first error since the following might be cascading.
+ result.output += errors[0].message + "\n"; // TODO: line, column?
}
if (!p_is_generating) {
result.passed = check_output(result.output);
@@ -498,9 +498,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
result.output = get_text_for_status(result.status) + "\n";
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
- for (const GDScriptParser::ParserError &E : errors) {
- result.output += E.message + "\n"; // TODO: line, column?
- break; // Only the first error since the following might be cascading.
+ if (!errors.is_empty()) {
+ // Only the first error since the following might be cascading.
+ result.output += errors[0].message + "\n"; // TODO: line, column?
}
if (!p_is_generating) {
result.passed = check_output(result.output);
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary.out b/modules/gdscript/tests/scripts/parser/features/dictionary.out
index 5f999f573a..e1eeb46f78 100644
--- a/modules/gdscript/tests/scripts/parser/features/dictionary.out
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary.out
@@ -7,8 +7,8 @@ null
false
empty array
zero Vector2i
-{22:{4:["nesting", "arrays"]}}
-{4:["nesting", "arrays"]}
+{ 22: { 4: ["nesting", "arrays"] } }
+{ 4: ["nesting", "arrays"] }
["nesting", "arrays"]
nesting
arrays
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out
index 5143d040a9..553d40d953 100644
--- a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out
@@ -1,2 +1,2 @@
GDTEST_OK
-{"a":1, "b":2, "with spaces":3, "2":4}
+{ "a": 1, "b": 2, "with spaces": 3, "2": 4 }
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out
index dd28609850..cf79845f53 100644
--- a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out
@@ -1,2 +1,2 @@
GDTEST_OK
-{"hello":{"world":{"is":"beautiful"}}}
+{ "hello": { "world": { "is": "beautiful" } } }
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out
index 8b8c33202f..508f0ff217 100644
--- a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out
+++ b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out
@@ -1,5 +1,5 @@
GDTEST_OK
-{8:{"key":"value"}}
-{"key":"value"}
+{ 8: { "key": "value" } }
+{ "key": "value" }
value
value
diff --git a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
index 5e7ccf534a..22929bf636 100644
--- a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
+++ b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
@@ -1,6 +1,6 @@
GDTEST_OK
-{1:(2, 0)}
-{3:(4, 0)}
+{ 1: (2, 0) }
+{ 3: (4, 0) }
[[(5, 0)]]
[[(6, 0)]]
[[(7, 0)]]
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out
index d4468737a5..1f33de00cc 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.out
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out
@@ -21,7 +21,7 @@ hello/world
RID(0)
Node::get_name
Node::[signal]property_list_changed
-{"hello":123}
+{ "hello": 123 }
["hello", 123]
[255, 0, 1]
[-1, 0, 1]
diff --git a/modules/gltf/config.py b/modules/gltf/config.py
index 189b5a831a..130c06d264 100644
--- a/modules/gltf/config.py
+++ b/modules/gltf/config.py
@@ -26,6 +26,7 @@ def get_doc_classes():
"GLTFSpecGloss",
"GLTFState",
"GLTFTexture",
+ "GLTFTextureSampler",
]
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index 6c2f488c1c..56f3a70631 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -93,6 +93,12 @@
<description>
</description>
</method>
+ <method name="get_texture_samplers">
+ <return type="GLTFTextureSampler[]" />
+ <description>
+ Retrieves the array of texture samplers that are used by the textures contained in the GLTF.
+ </description>
+ </method>
<method name="get_textures">
<return type="GLTFTexture[]" />
<description>
@@ -180,6 +186,13 @@
<description>
</description>
</method>
+ <method name="set_texture_samplers">
+ <return type="void" />
+ <param index="0" name="texture_samplers" type="GLTFTextureSampler[]" />
+ <description>
+ Sets the array of texture samplers that are used by the textures contained in the GLTF.
+ </description>
+ </method>
<method name="set_textures">
<return type="void" />
<param index="0" name="textures" type="GLTFTexture[]" />
diff --git a/modules/gltf/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml
index c0bc424168..f4486fd504 100644
--- a/modules/gltf/doc_classes/GLTFTexture.xml
+++ b/modules/gltf/doc_classes/GLTFTexture.xml
@@ -7,6 +7,9 @@
<tutorials>
</tutorials>
<members>
+ <member name="sampler" type="int" setter="set_sampler" getter="get_sampler" default="-1">
+ ID of the texture sampler to use when sampling the image. If -1, then the default texture sampler is used (linear filtering, and repeat wrapping in both axes).
+ </member>
<member name="src_image" type="int" setter="set_src_image" getter="get_src_image" default="0">
</member>
</members>
diff --git a/modules/gltf/doc_classes/GLTFTextureSampler.xml b/modules/gltf/doc_classes/GLTFTextureSampler.xml
new file mode 100644
index 0000000000..1cc69bfe73
--- /dev/null
+++ b/modules/gltf/doc_classes/GLTFTextureSampler.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFTextureSampler" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ Represents a GLTF texture sampler
+ </brief_description>
+ <description>
+ Represents a texture sampler as defined by the base GLTF spec. Texture samplers in GLTF specify how to sample data from the texture's base image, when rendering the texture on an object.
+ </description>
+ <tutorials>
+ </tutorials>
+ <members>
+ <member name="mag_filter" type="int" setter="set_mag_filter" getter="get_mag_filter" default="9729">
+ Texture's magnification filter, used when texture appears larger on screen than the source image.
+ </member>
+ <member name="min_filter" type="int" setter="set_min_filter" getter="get_min_filter" default="9987">
+ Texture's minification filter, used when the texture appears smaller on screen than the source image.
+ </member>
+ <member name="wrap_s" type="int" setter="set_wrap_s" getter="get_wrap_s" default="10497">
+ Wrapping mode to use for S-axis (horizontal) texture coordinates.
+ </member>
+ <member name="wrap_t" type="int" setter="set_wrap_t" getter="get_wrap_t" default="10497">
+ Wrapping mode to use for T-axis (vertical) texture coordinates.
+ </member>
+ </members>
+</class>
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index f79731dd22..20c9508474 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -77,20 +77,19 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
} else {
parameters_arg += "export_extras=False,";
}
- if (p_options.has(SNAME("blender/meshes/skins")) && p_options[SNAME("blender/meshes/skins")]) {
+ if (p_options.has(SNAME("blender/meshes/skins"))) {
int32_t skins = p_options["blender/meshes/skins"];
if (skins == BLEND_BONE_INFLUENCES_NONE) {
- parameters_arg += "export_all_influences=False,";
+ parameters_arg += "export_skins=False,";
} else if (skins == BLEND_BONE_INFLUENCES_COMPATIBLE) {
- parameters_arg += "export_all_influences=False,";
+ parameters_arg += "export_all_influences=False,export_skins=True,";
} else if (skins == BLEND_BONE_INFLUENCES_ALL) {
- parameters_arg += "export_all_influences=True,";
+ parameters_arg += "export_all_influences=True,export_skins=True,";
}
- parameters_arg += "export_skins=True,";
} else {
parameters_arg += "export_skins=False,";
}
- if (p_options.has(SNAME("blender/materials/export_materials")) && p_options[SNAME("blender/materials/export_materials")]) {
+ if (p_options.has(SNAME("blender/materials/export_materials"))) {
int32_t exports = p_options["blender/materials/export_materials"];
if (exports == BLEND_MATERIAL_EXPORT_PLACEHOLDER) {
parameters_arg += "export_materials='PLACEHOLDER',";
@@ -115,7 +114,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
} else {
parameters_arg += "export_colors=False,";
}
- if (p_options.has(SNAME("blender/nodes/visible")) && p_options[SNAME("blender/nodes/visible")]) {
+ if (p_options.has(SNAME("blender/nodes/visible"))) {
int32_t visible = p_options["blender/nodes/visible"];
if (visible == BLEND_VISIBLE_VISIBLE_ONLY) {
parameters_arg += "use_visible=True,";
@@ -180,13 +179,13 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
"export_format='GLTF_SEPARATE',"
"export_yup=True," +
parameters_arg;
- String script =
+ String export_script =
String("import bpy, sys;") +
"print('Blender 3.0 or higher is required.', file=sys.stderr) if bpy.app.version < (3, 0, 0) else None;" +
vformat("bpy.ops.wm.open_mainfile(filepath='%s');", source_global) +
unpack_all +
vformat("bpy.ops.export_scene.gltf(export_keep_originals=True,%s);", common_args);
- print_verbose(script);
+ print_verbose(export_script);
// Run script with configured Blender binary.
@@ -201,7 +200,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
List<String> args;
args.push_back("--background");
args.push_back("--python-expr");
- args.push_back(script);
+ args.push_back(export_script);
String standard_out;
int ret;
@@ -350,9 +349,7 @@ static bool _test_blender_path(const String &p_path, String *r_err = nullptr) {
bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const {
bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled");
- String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
-
- if (blend_enabled && !_test_blender_path(blender_path)) {
+ if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender3_path").operator String())) {
// Intending to import Blender, but blend not configured.
return true;
}
diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp
index 6923c765cb..d00bead61c 100644
--- a/modules/gltf/extensions/gltf_light.cpp
+++ b/modules/gltf/extensions/gltf_light.cpp
@@ -142,18 +142,17 @@ Light3D *GLTFLight::to_node() const {
light->set_color(color);
return light;
}
- const float range = CLAMP(this->range, 0, 4096);
if (light_type == "point") {
OmniLight3D *light = memnew(OmniLight3D);
light->set_param(OmniLight3D::PARAM_ENERGY, intensity);
- light->set_param(OmniLight3D::PARAM_RANGE, range);
+ light->set_param(OmniLight3D::PARAM_RANGE, CLAMP(range, 0, 4096));
light->set_color(color);
return light;
}
if (light_type == "spot") {
SpotLight3D *light = memnew(SpotLight3D);
light->set_param(SpotLight3D::PARAM_ENERGY, intensity);
- light->set_param(SpotLight3D::PARAM_RANGE, range);
+ light->set_param(SpotLight3D::PARAM_RANGE, CLAMP(range, 0, 4096));
light->set_param(SpotLight3D::PARAM_SPOT_ANGLE, Math::rad_to_deg(outer_cone_angle));
light->set_color(color);
// Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b
diff --git a/modules/gltf/gltf_defines.h b/modules/gltf/gltf_defines.h
index 9ee2397968..23bf33869e 100644
--- a/modules/gltf/gltf_defines.h
+++ b/modules/gltf/gltf_defines.h
@@ -58,6 +58,7 @@ class GLTFSkin;
class GLTFSpecGloss;
class GLTFState;
class GLTFTexture;
+class GLTFTextureSampler;
// GLTF index aliases.
using GLTFAccessorIndex = int;
@@ -73,6 +74,7 @@ using GLTFNodeIndex = int;
using GLTFSkeletonIndex = int;
using GLTFSkinIndex = int;
using GLTFTextureIndex = int;
+using GLTFTextureSamplerIndex = int;
enum GLTFType {
TYPE_SCALAR,
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 8d2e37be3a..6700b6de0a 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -145,6 +145,12 @@ Error GLTFDocument::_serialize(Ref<GLTFState> state, const String &p_path) {
return Error::FAILED;
}
+ /* STEP SERIALIZE TEXTURE SAMPLERS */
+ err = _serialize_texture_samplers(state);
+ if (err != OK) {
+ return Error::FAILED;
+ }
+
/* STEP SERIALIZE ANIMATIONS */
err = _serialize_animations(state);
if (err != OK) {
@@ -465,23 +471,23 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> state) {
String GLTFDocument::_gen_unique_name(Ref<GLTFState> state, const String &p_name) {
const String s_name = p_name.validate_node_name();
- String name;
+ String u_name;
int index = 1;
while (true) {
- name = s_name;
+ u_name = s_name;
if (index > 1) {
- name += itos(index);
+ u_name += itos(index);
}
- if (!state->unique_names.has(name)) {
+ if (!state->unique_names.has(u_name)) {
break;
}
index++;
}
- state->unique_names.insert(name);
+ state->unique_names.insert(u_name);
- return name;
+ return u_name;
}
String GLTFDocument::_sanitize_animation_name(const String &p_name) {
@@ -489,39 +495,39 @@ String GLTFDocument::_sanitize_animation_name(const String &p_name) {
// (See animation/animation_player.cpp::add_animation)
// TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node.
- String name = p_name.validate_node_name();
- name = name.replace(",", "");
- name = name.replace("[", "");
- return name;
+ String anim_name = p_name.validate_node_name();
+ anim_name = anim_name.replace(",", "");
+ anim_name = anim_name.replace("[", "");
+ return anim_name;
}
String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> state, const String &p_name) {
const String s_name = _sanitize_animation_name(p_name);
- String name;
+ String u_name;
int index = 1;
while (true) {
- name = s_name;
+ u_name = s_name;
if (index > 1) {
- name += itos(index);
+ u_name += itos(index);
}
- if (!state->unique_animation_names.has(name)) {
+ if (!state->unique_animation_names.has(u_name)) {
break;
}
index++;
}
- state->unique_animation_names.insert(name);
+ state->unique_animation_names.insert(u_name);
- return name;
+ return u_name;
}
String GLTFDocument::_sanitize_bone_name(const String &p_name) {
- String name = p_name;
- name = name.replace(":", "_");
- name = name.replace("/", "_");
- return name;
+ String bone_name = p_name;
+ bone_name = bone_name.replace(":", "_");
+ bone_name = bone_name.replace("/", "_");
+ return bone_name;
}
String GLTFDocument::_gen_unique_bone_name(Ref<GLTFState> state, const GLTFSkeletonIndex skel_i, const String &p_name) {
@@ -529,23 +535,23 @@ String GLTFDocument::_gen_unique_bone_name(Ref<GLTFState> state, const GLTFSkele
if (s_name.is_empty()) {
s_name = "bone";
}
- String name;
+ String u_name;
int index = 1;
while (true) {
- name = s_name;
+ u_name = s_name;
if (index > 1) {
- name += "_" + itos(index);
+ u_name += "_" + itos(index);
}
- if (!state->skeletons[skel_i]->unique_names.has(name)) {
+ if (!state->skeletons[skel_i]->unique_names.has(u_name)) {
break;
}
index++;
}
- state->skeletons.write[skel_i]->unique_names.insert(name);
+ state->skeletons.write[skel_i]->unique_names.insert(u_name);
- return name;
+ return u_name;
}
Error GLTFDocument::_parse_scenes(Ref<GLTFState> state) {
@@ -2584,10 +2590,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) {
Mesh::PRIMITIVE_TRIANGLES, // 4 TRIANGLES
Mesh::PRIMITIVE_TRIANGLE_STRIP, // 5 TRIANGLE_STRIP
Mesh::PRIMITIVE_TRIANGLES, // 6 TRIANGLE_FAN fan not supported, should be converted
-#ifndef _MSC_VER
-#warning line loop and triangle fan are not supported and need to be converted to lines and triangles
-#endif
-
+ // TODO: Line loop and triangle fan are not supported and need to be converted to lines and triangles.
};
primitive = primitives2[mode];
@@ -2818,8 +2821,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) {
if (j == 0) {
const Array &target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array();
for (int k = 0; k < targets.size(); k++) {
- const String name = k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k);
- import_mesh->add_blend_shape(name);
+ import_mesh->add_blend_shape(k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k));
}
}
@@ -3028,12 +3030,12 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path
d["mimeType"] = "image/png";
} else {
ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
- String name = state->images[i]->get_name();
- if (name.is_empty()) {
- name = itos(i);
+ String img_name = state->images[i]->get_name();
+ if (img_name.is_empty()) {
+ img_name = itos(i);
}
- name = _gen_unique_name(state, name);
- name = name.pad_zeros(3) + ".png";
+ img_name = _gen_unique_name(state, img_name);
+ img_name = img_name.pad_zeros(3) + ".png";
String texture_dir = "textures";
String path = p_path.get_base_dir();
String new_texture_dir = path + "/" + texture_dir;
@@ -3041,8 +3043,8 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path
if (!da->dir_exists(new_texture_dir)) {
da->make_dir(new_texture_dir);
}
- image->save_png(new_texture_dir.path_join(name));
- d["uri"] = texture_dir.path_join(name).uri_encode();
+ image->save_png(new_texture_dir.path_join(img_name));
+ d["uri"] = texture_dir.path_join(img_name).uri_encode();
}
images.push_back(d);
}
@@ -3219,6 +3221,11 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> state) {
Ref<GLTFTexture> t = state->textures[i];
ERR_CONTINUE(t->get_src_image() == -1);
d["source"] = t->get_src_image();
+
+ GLTFTextureSamplerIndex sampler_index = t->get_sampler();
+ if (sampler_index != -1) {
+ d["sampler"] = sampler_index;
+ }
textures.push_back(d);
}
state->json["textures"] = textures;
@@ -3240,13 +3247,18 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> state) {
Ref<GLTFTexture> t;
t.instantiate();
t->set_src_image(d["source"]);
+ if (d.has("sampler")) {
+ t->set_sampler(d["sampler"]);
+ } else {
+ t->set_sampler(-1);
+ }
state->textures.push_back(t);
}
return OK;
}
-GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture) {
+GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) {
ERR_FAIL_COND_V(p_texture.is_null(), -1);
Ref<GLTFTexture> gltf_texture;
gltf_texture.instantiate();
@@ -3254,6 +3266,7 @@ GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D>
GLTFImageIndex gltf_src_image_i = state->images.size();
state->images.push_back(p_texture);
gltf_texture->set_src_image(gltf_src_image_i);
+ gltf_texture->set_sampler(_set_sampler_for_mode(state, p_filter_mode, p_repeats));
GLTFTextureIndex gltf_texture_i = state->textures.size();
state->textures.push_back(gltf_texture);
return gltf_texture_i;
@@ -3268,6 +3281,102 @@ Ref<Texture2D> GLTFDocument::_get_texture(Ref<GLTFState> state, const GLTFTextur
return state->images[image];
}
+GLTFTextureSamplerIndex GLTFDocument::_set_sampler_for_mode(Ref<GLTFState> state, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) {
+ for (int i = 0; i < state->texture_samplers.size(); ++i) {
+ if (state->texture_samplers[i]->get_filter_mode() == p_filter_mode) {
+ return i;
+ }
+ }
+
+ GLTFTextureSamplerIndex gltf_sampler_i = state->texture_samplers.size();
+ Ref<GLTFTextureSampler> gltf_sampler;
+ gltf_sampler.instantiate();
+ gltf_sampler->set_filter_mode(p_filter_mode);
+ gltf_sampler->set_wrap_mode(p_repeats);
+ state->texture_samplers.push_back(gltf_sampler);
+ return gltf_sampler_i;
+}
+
+Ref<GLTFTextureSampler> GLTFDocument::_get_sampler_for_texture(Ref<GLTFState> state, const GLTFTextureIndex p_texture) {
+ ERR_FAIL_INDEX_V(p_texture, state->textures.size(), Ref<Texture2D>());
+ const GLTFTextureSamplerIndex sampler = state->textures[p_texture]->get_sampler();
+
+ if (sampler == -1) {
+ return state->default_texture_sampler;
+ } else {
+ ERR_FAIL_INDEX_V(sampler, state->texture_samplers.size(), Ref<GLTFTextureSampler>());
+
+ return state->texture_samplers[sampler];
+ }
+}
+
+Error GLTFDocument::_serialize_texture_samplers(Ref<GLTFState> state) {
+ if (!state->texture_samplers.size()) {
+ return OK;
+ }
+
+ Array samplers;
+ for (int32_t i = 0; i < state->texture_samplers.size(); ++i) {
+ Dictionary d;
+ Ref<GLTFTextureSampler> s = state->texture_samplers[i];
+ d["magFilter"] = s->get_mag_filter();
+ d["minFilter"] = s->get_min_filter();
+ d["wrapS"] = s->get_wrap_s();
+ d["wrapT"] = s->get_wrap_t();
+ samplers.push_back(d);
+ }
+ state->json["samplers"] = samplers;
+
+ return OK;
+}
+
+Error GLTFDocument::_parse_texture_samplers(Ref<GLTFState> state) {
+ state->default_texture_sampler.instantiate();
+ state->default_texture_sampler->set_min_filter(GLTFTextureSampler::FilterMode::LINEAR_MIPMAP_LINEAR);
+ state->default_texture_sampler->set_mag_filter(GLTFTextureSampler::FilterMode::LINEAR);
+ state->default_texture_sampler->set_wrap_s(GLTFTextureSampler::WrapMode::REPEAT);
+ state->default_texture_sampler->set_wrap_t(GLTFTextureSampler::WrapMode::REPEAT);
+
+ if (!state->json.has("samplers")) {
+ return OK;
+ }
+
+ const Array &samplers = state->json["samplers"];
+ for (int i = 0; i < samplers.size(); ++i) {
+ const Dictionary &d = samplers[i];
+
+ Ref<GLTFTextureSampler> sampler;
+ sampler.instantiate();
+
+ if (d.has("minFilter")) {
+ sampler->set_min_filter(d["minFilter"]);
+ } else {
+ sampler->set_min_filter(GLTFTextureSampler::FilterMode::LINEAR_MIPMAP_LINEAR);
+ }
+ if (d.has("magFilter")) {
+ sampler->set_mag_filter(d["magFilter"]);
+ } else {
+ sampler->set_mag_filter(GLTFTextureSampler::FilterMode::LINEAR);
+ }
+
+ if (d.has("wrapS")) {
+ sampler->set_wrap_s(d["wrapS"]);
+ } else {
+ sampler->set_wrap_s(GLTFTextureSampler::WrapMode::DEFAULT);
+ }
+
+ if (d.has("wrapT")) {
+ sampler->set_wrap_t(d["wrapT"]);
+ } else {
+ sampler->set_wrap_t(GLTFTextureSampler::WrapMode::DEFAULT);
+ }
+
+ state->texture_samplers.push_back(sampler);
+ }
+
+ return OK;
+}
+
Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
Array materials;
for (int32_t i = 0; i < state->materials.size(); i++) {
@@ -3299,7 +3408,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
albedo_texture->set_name(material->get_name() + "_albedo");
- gltf_texture_index = _set_texture(state, albedo_texture);
+ gltf_texture_index = _set_texture(state, albedo_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (gltf_texture_index != -1) {
bct["index"] = gltf_texture_index;
@@ -3429,7 +3538,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
GLTFTextureIndex orm_texture_index = -1;
if (has_ao || has_roughness || has_metalness) {
orm_texture->set_name(material->get_name() + "_orm");
- orm_texture_index = _set_texture(state, orm_texture);
+ orm_texture_index = _set_texture(state, orm_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (has_ao) {
Dictionary occt;
@@ -3484,7 +3593,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
GLTFTextureIndex gltf_texture_index = -1;
if (tex.is_valid() && tex->get_image().is_valid()) {
tex->set_name(material->get_name() + "_normal");
- gltf_texture_index = _set_texture(state, tex);
+ gltf_texture_index = _set_texture(state, tex, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
nt["scale"] = material->get_normal_scale();
if (gltf_texture_index != -1) {
@@ -3507,7 +3616,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
GLTFTextureIndex gltf_texture_index = -1;
if (emission_texture.is_valid() && emission_texture->get_image().is_valid()) {
emission_texture->set_name(material->get_name() + "_emission");
- gltf_texture_index = _set_texture(state, emission_texture);
+ gltf_texture_index = _set_texture(state, emission_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (gltf_texture_index != -1) {
@@ -3566,6 +3675,11 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) {
if (sgm.has("diffuseTexture")) {
const Dictionary &diffuse_texture_dict = sgm["diffuseTexture"];
if (diffuse_texture_dict.has("index")) {
+ Ref<GLTFTextureSampler> diffuse_sampler = _get_sampler_for_texture(state, diffuse_texture_dict["index"]);
+ if (diffuse_sampler.is_valid()) {
+ material->set_texture_filter(diffuse_sampler->get_filter_mode());
+ material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, diffuse_sampler->get_wrap_mode());
+ }
Ref<Texture2D> diffuse_texture = _get_texture(state, diffuse_texture_dict["index"]);
if (diffuse_texture.is_valid()) {
spec_gloss->diffuse_img = diffuse_texture->get_image();
@@ -3614,6 +3728,9 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) {
if (mr.has("baseColorTexture")) {
const Dictionary &bct = mr["baseColorTexture"];
if (bct.has("index")) {
+ Ref<GLTFTextureSampler> bct_sampler = _get_sampler_for_texture(state, bct["index"]);
+ material->set_texture_filter(bct_sampler->get_filter_mode());
+ material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, bct_sampler->get_wrap_mode());
material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, _get_texture(state, bct["index"]));
}
if (!mr.has("baseColorFactor")) {
@@ -4301,14 +4418,14 @@ Error GLTFDocument::_determine_skeleton_roots(Ref<GLTFState> state, const GLTFSk
Ref<GLTFSkeleton> skeleton = state->skeletons.write[skel_i];
- Vector<GLTFNodeIndex> owners;
- disjoint_set.get_representatives(owners);
+ Vector<GLTFNodeIndex> representatives;
+ disjoint_set.get_representatives(representatives);
Vector<GLTFNodeIndex> roots;
- for (int i = 0; i < owners.size(); ++i) {
+ for (int i = 0; i < representatives.size(); ++i) {
Vector<GLTFNodeIndex> set;
- disjoint_set.get_members(set, owners[i]);
+ disjoint_set.get_members(set, representatives[i]);
const GLTFNodeIndex root = _find_highest_node(state, set);
ERR_FAIL_COND_V(root < 0, FAILED);
roots.push_back(root);
@@ -4839,12 +4956,12 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> state) {
Array samplers = d["samplers"];
if (d.has("name")) {
- const String name = d["name"];
- const String name_lower = name.to_lower();
- if (name_lower.begins_with("loop") || name_lower.ends_with("loop") || name_lower.begins_with("cycle") || name_lower.ends_with("cycle")) {
+ const String anim_name = d["name"];
+ const String anim_name_lower = anim_name.to_lower();
+ if (anim_name_lower.begins_with("loop") || anim_name_lower.ends_with("loop") || anim_name_lower.begins_with("cycle") || anim_name_lower.ends_with("cycle")) {
animation->set_loop(true);
}
- animation->set_name(_gen_unique_animation_name(state, name));
+ animation->set_name(_gen_unique_animation_name(state, anim_name));
}
for (int j = 0; j < channels.size(); j++) {
@@ -5707,15 +5824,15 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T
void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) {
Ref<GLTFAnimation> anim = state->animations[index];
- String name = anim->get_name();
- if (name.is_empty()) {
+ String anim_name = anim->get_name();
+ if (anim_name.is_empty()) {
// No node represent these, and they are not in the hierarchy, so just make a unique name
- name = _gen_unique_name(state, "Animation");
+ anim_name = _gen_unique_name(state, "Animation");
}
Ref<Animation> animation;
animation.instantiate();
- animation->set_name(name);
+ animation->set_name(anim_name);
if (anim->get_loop()) {
animation->set_loop_mode(Animation::LOOP_LINEAR);
@@ -5938,7 +6055,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
} else {
library = ap->get_animation_library("");
}
- library->add_animation(name, animation);
+ library->add_animation(anim_name, animation);
}
void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) {
@@ -6477,8 +6594,10 @@ Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess>
err = ext->import_preflight(state);
ERR_FAIL_COND_V(err != OK, err);
}
+
err = _parse_gltf_state(state, p_path, p_bake_fps);
ERR_FAIL_COND_V(err != OK, err);
+
return OK;
}
@@ -6834,6 +6953,11 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_sear
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
+ /* PARSE TEXTURE SAMPLERS */
+ err = _parse_texture_samplers(state);
+
+ ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
+
/* PARSE TEXTURES */
err = _parse_textures(state);
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 750d3d403e..62b6e29fe0 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -95,9 +95,14 @@ private:
String _gen_unique_bone_name(Ref<GLTFState> state,
const GLTFSkeletonIndex skel_i,
const String &p_name);
- GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture);
+ GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture,
+ StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats);
Ref<Texture2D> _get_texture(Ref<GLTFState> state,
const GLTFTextureIndex p_texture);
+ GLTFTextureSamplerIndex _set_sampler_for_mode(Ref<GLTFState> state,
+ StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats);
+ Ref<GLTFTextureSampler> _get_sampler_for_texture(Ref<GLTFState> state,
+ const GLTFTextureIndex p_texture);
Error _parse_json(const String &p_path, Ref<GLTFState> state);
Error _parse_glb(Ref<FileAccess> f, Ref<GLTFState> state);
void _compute_node_heights(Ref<GLTFState> state);
@@ -145,10 +150,12 @@ private:
const bool p_for_vertex);
Error _parse_meshes(Ref<GLTFState> state);
Error _serialize_textures(Ref<GLTFState> state);
+ Error _serialize_texture_samplers(Ref<GLTFState> state);
Error _serialize_images(Ref<GLTFState> state, const String &p_path);
Error _serialize_lights(Ref<GLTFState> state);
Error _parse_images(Ref<GLTFState> state, const String &p_base_path);
Error _parse_textures(Ref<GLTFState> state);
+ Error _parse_texture_samplers(Ref<GLTFState> state);
Error _parse_materials(Ref<GLTFState> state);
void _set_texture_transform_uv1(const Dictionary &d, Ref<BaseMaterial3D> material);
void spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss,
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index a23fb39503..60192c67e6 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -64,6 +64,8 @@ void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_nodes", "root_nodes"), &GLTFState::set_root_nodes);
ClassDB::bind_method(D_METHOD("get_textures"), &GLTFState::get_textures);
ClassDB::bind_method(D_METHOD("set_textures", "textures"), &GLTFState::set_textures);
+ ClassDB::bind_method(D_METHOD("get_texture_samplers"), &GLTFState::get_texture_samplers);
+ ClassDB::bind_method(D_METHOD("set_texture_samplers", "texture_samplers"), &GLTFState::set_texture_samplers);
ClassDB::bind_method(D_METHOD("get_images"), &GLTFState::get_images);
ClassDB::bind_method(D_METHOD("set_images", "images"), &GLTFState::set_images);
ClassDB::bind_method(D_METHOD("get_skins"), &GLTFState::get_skins);
@@ -101,6 +103,7 @@ void GLTFState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_path"), "set_base_path", "get_base_path"); // String
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "root_nodes"), "set_root_nodes", "get_root_nodes"); // Vector<int>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_textures", "get_textures"); // Vector<Ref<GLTFTexture>>
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "texture_samplers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_texture_samplers", "get_texture_samplers"); //Vector<Ref<GLTFTextureSampler>>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "images", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_images", "get_images"); // Vector<Ref<Texture>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skins", "get_skins"); // Vector<Ref<GLTFSkin>>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "cameras", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_cameras", "get_cameras"); // Vector<Ref<GLTFCamera>>
@@ -236,6 +239,14 @@ void GLTFState::set_textures(TypedArray<GLTFTexture> p_textures) {
GLTFTemplateConvert::set_from_array(textures, p_textures);
}
+TypedArray<GLTFTextureSampler> GLTFState::get_texture_samplers() {
+ return GLTFTemplateConvert::to_array(texture_samplers);
+}
+
+void GLTFState::set_texture_samplers(TypedArray<GLTFTextureSampler> p_texture_samplers) {
+ GLTFTemplateConvert::set_from_array(texture_samplers, p_texture_samplers);
+}
+
TypedArray<Texture2D> GLTFState::get_images() {
return GLTFTemplateConvert::to_array(images);
}
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index 791431f376..afe7e82010 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -42,6 +42,7 @@
#include "structures/gltf_skeleton.h"
#include "structures/gltf_skin.h"
#include "structures/gltf_texture.h"
+#include "structures/gltf_texture_sampler.h"
#include "core/templates/rb_map.h"
#include "scene/animation/animation_player.h"
@@ -77,6 +78,8 @@ class GLTFState : public Resource {
String scene_name;
Vector<int> root_nodes;
Vector<Ref<GLTFTexture>> textures;
+ Vector<Ref<GLTFTextureSampler>> texture_samplers;
+ Ref<GLTFTextureSampler> default_texture_sampler;
Vector<Ref<Texture2D>> images;
Vector<String> extensions_used;
Vector<String> extensions_required;
@@ -149,6 +152,9 @@ public:
TypedArray<GLTFTexture> get_textures();
void set_textures(TypedArray<GLTFTexture> p_textures);
+ TypedArray<GLTFTextureSampler> get_texture_samplers();
+ void set_texture_samplers(TypedArray<GLTFTextureSampler> p_texture_samplers);
+
TypedArray<Texture2D> get_images();
void set_images(TypedArray<Texture2D> p_images);
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index 6e7f7d6fed..b9027f6e3d 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -47,6 +47,7 @@
#include "structures/gltf_skeleton.h"
#include "structures/gltf_skin.h"
#include "structures/gltf_texture.h"
+#include "structures/gltf_texture_sampler.h"
#ifdef TOOLS_ENABLED
#include "core/config/project_settings.h"
@@ -126,6 +127,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(GLTFSpecGloss);
GDREGISTER_CLASS(GLTFState);
GDREGISTER_CLASS(GLTFTexture);
+ GDREGISTER_CLASS(GLTFTextureSampler);
}
#ifdef TOOLS_ENABLED
diff --git a/modules/gltf/structures/gltf_texture.cpp b/modules/gltf/structures/gltf_texture.cpp
index 2a21cb3df8..5cc96e3221 100644
--- a/modules/gltf/structures/gltf_texture.cpp
+++ b/modules/gltf/structures/gltf_texture.cpp
@@ -33,8 +33,11 @@
void GLTFTexture::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_src_image"), &GLTFTexture::get_src_image);
ClassDB::bind_method(D_METHOD("set_src_image", "src_image"), &GLTFTexture::set_src_image);
+ ClassDB::bind_method(D_METHOD("get_sampler"), &GLTFTexture::get_sampler);
+ ClassDB::bind_method(D_METHOD("set_sampler", "sampler"), &GLTFTexture::set_sampler);
ADD_PROPERTY(PropertyInfo(Variant::INT, "src_image"), "set_src_image", "get_src_image"); // int
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "sampler"), "set_sampler", "get_sampler"); // int
}
GLTFImageIndex GLTFTexture::get_src_image() const {
@@ -44,3 +47,11 @@ GLTFImageIndex GLTFTexture::get_src_image() const {
void GLTFTexture::set_src_image(GLTFImageIndex val) {
src_image = val;
}
+
+GLTFTextureSamplerIndex GLTFTexture::get_sampler() const {
+ return sampler;
+}
+
+void GLTFTexture::set_sampler(GLTFTextureSamplerIndex val) {
+ sampler = val;
+}
diff --git a/modules/gltf/structures/gltf_texture.h b/modules/gltf/structures/gltf_texture.h
index b1d12dddfa..b26c8a7b07 100644
--- a/modules/gltf/structures/gltf_texture.h
+++ b/modules/gltf/structures/gltf_texture.h
@@ -39,6 +39,7 @@ class GLTFTexture : public Resource {
private:
GLTFImageIndex src_image = 0;
+ GLTFTextureSamplerIndex sampler = -1;
protected:
static void _bind_methods();
@@ -46,6 +47,8 @@ protected:
public:
GLTFImageIndex get_src_image() const;
void set_src_image(GLTFImageIndex val);
+ GLTFTextureSamplerIndex get_sampler() const;
+ void set_sampler(GLTFTextureSamplerIndex val);
};
#endif // GLTF_TEXTURE_H
diff --git a/modules/websocket/websocket_client.h b/modules/gltf/structures/gltf_texture_sampler.cpp
index e747aee4e4..6cf615b6cc 100644
--- a/modules/websocket/websocket_client.h
+++ b/modules/gltf/structures/gltf_texture_sampler.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* websocket_client.h */
+/* gltf_texture_sampler.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,48 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef WEBSOCKET_CLIENT_H
-#define WEBSOCKET_CLIENT_H
-
-#include "core/crypto/crypto.h"
-#include "core/error/error_list.h"
-#include "websocket_multiplayer_peer.h"
-#include "websocket_peer.h"
-
-class WebSocketClient : public WebSocketMultiplayerPeer {
- GDCLASS(WebSocketClient, WebSocketMultiplayerPeer);
- GDCICLASS(WebSocketClient);
-
-protected:
- Ref<WebSocketPeer> _peer;
- bool verify_tls = true;
- Ref<X509Certificate> tls_cert;
-
- static void _bind_methods();
-
-public:
- Error connect_to_url(String p_url, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false, const Vector<String> p_custom_headers = Vector<String>());
-
- void set_verify_tls_enabled(bool p_verify_tls);
- bool is_verify_tls_enabled() const;
- Ref<X509Certificate> get_trusted_tls_certificate() const;
- void set_trusted_tls_certificate(Ref<X509Certificate> p_cert);
-
- virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()) = 0;
- virtual void disconnect_from_host(int p_code = 1000, String p_reason = "") = 0;
- virtual IPAddress get_connected_host() const = 0;
- virtual uint16_t get_connected_port() const = 0;
-
- virtual bool is_server() const override;
-
- void _on_peer_packet();
- void _on_connect(String p_protocol);
- void _on_close_request(int p_code, String p_reason);
- void _on_disconnect(bool p_was_clean);
- void _on_error();
-
- WebSocketClient();
- ~WebSocketClient();
-};
-
-#endif // WEBSOCKET_CLIENT_H
+#include "gltf_texture_sampler.h"
+
+void GLTFTextureSampler::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_mag_filter"), &GLTFTextureSampler::get_mag_filter);
+ ClassDB::bind_method(D_METHOD("set_mag_filter", "filter_mode"), &GLTFTextureSampler::set_mag_filter);
+ ClassDB::bind_method(D_METHOD("get_min_filter"), &GLTFTextureSampler::get_min_filter);
+ ClassDB::bind_method(D_METHOD("set_min_filter", "filter_mode"), &GLTFTextureSampler::set_min_filter);
+ ClassDB::bind_method(D_METHOD("get_wrap_s"), &GLTFTextureSampler::get_wrap_s);
+ ClassDB::bind_method(D_METHOD("set_wrap_s", "wrap_mode"), &GLTFTextureSampler::set_wrap_s);
+ ClassDB::bind_method(D_METHOD("get_wrap_t"), &GLTFTextureSampler::get_wrap_t);
+ ClassDB::bind_method(D_METHOD("set_wrap_t", "wrap_mode"), &GLTFTextureSampler::set_wrap_t);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "mag_filter"), "set_mag_filter", "get_mag_filter");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "min_filter"), "set_min_filter", "get_min_filter");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_s"), "set_wrap_s", "get_wrap_s");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_t"), "set_wrap_t", "get_wrap_t");
+}
diff --git a/modules/gltf/structures/gltf_texture_sampler.h b/modules/gltf/structures/gltf_texture_sampler.h
new file mode 100644
index 0000000000..3fad31bbee
--- /dev/null
+++ b/modules/gltf/structures/gltf_texture_sampler.h
@@ -0,0 +1,163 @@
+/*************************************************************************/
+/* gltf_texture_sampler.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 GLTF_TEXTURE_SAMPLER_H
+#define GLTF_TEXTURE_SAMPLER_H
+
+#include "core/io/resource.h"
+#include "scene/resources/material.h"
+
+class GLTFTextureSampler : public Resource {
+ GDCLASS(GLTFTextureSampler, Resource);
+
+public:
+ enum FilterMode {
+ NEAREST = 9728,
+ LINEAR = 9729,
+ NEAREST_MIPMAP_NEAREST = 9984,
+ LINEAR_MIPMAP_NEAREST = 9985,
+ NEAREST_MIPMAP_LINEAR = 9986,
+ LINEAR_MIPMAP_LINEAR = 9987
+ };
+
+ enum WrapMode {
+ CLAMP_TO_EDGE = 33071,
+ MIRRORED_REPEAT = 33648,
+ REPEAT = 10497,
+ DEFAULT = REPEAT
+ };
+
+ int get_mag_filter() const {
+ return mag_filter;
+ }
+
+ void set_mag_filter(const int filter_mode) {
+ mag_filter = (FilterMode)filter_mode;
+ }
+
+ int get_min_filter() const {
+ return min_filter;
+ }
+
+ void set_min_filter(const int filter_mode) {
+ min_filter = (FilterMode)filter_mode;
+ }
+
+ int get_wrap_s() const {
+ return wrap_s;
+ }
+
+ void set_wrap_s(const int wrap_mode) {
+ wrap_s = (WrapMode)wrap_mode;
+ }
+
+ int get_wrap_t() const {
+ return wrap_t;
+ }
+
+ void set_wrap_t(const int wrap_mode) {
+ wrap_s = (WrapMode)wrap_mode;
+ }
+
+ StandardMaterial3D::TextureFilter get_filter_mode() const {
+ using TextureFilter = StandardMaterial3D::TextureFilter;
+
+ switch (min_filter) {
+ case NEAREST:
+ return TextureFilter::TEXTURE_FILTER_NEAREST;
+ case LINEAR:
+ return TextureFilter::TEXTURE_FILTER_LINEAR;
+ case NEAREST_MIPMAP_NEAREST:
+ case NEAREST_MIPMAP_LINEAR:
+ return TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS;
+ case LINEAR_MIPMAP_NEAREST:
+ case LINEAR_MIPMAP_LINEAR:
+ default:
+ return TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS;
+ }
+ }
+
+ void set_filter_mode(StandardMaterial3D::TextureFilter mode) {
+ using TextureFilter = StandardMaterial3D::TextureFilter;
+
+ switch (mode) {
+ case TextureFilter::TEXTURE_FILTER_NEAREST:
+ min_filter = FilterMode::NEAREST;
+ mag_filter = FilterMode::NEAREST;
+ break;
+ case TextureFilter::TEXTURE_FILTER_LINEAR:
+ min_filter = FilterMode::LINEAR;
+ mag_filter = FilterMode::LINEAR;
+ break;
+ case TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS:
+ case TextureFilter::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC:
+ min_filter = FilterMode::NEAREST_MIPMAP_LINEAR;
+ mag_filter = FilterMode::NEAREST;
+ break;
+ case TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS:
+ case TextureFilter::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC:
+ default:
+ min_filter = FilterMode::LINEAR_MIPMAP_LINEAR;
+ mag_filter = FilterMode::LINEAR;
+ break;
+ }
+ }
+
+ bool get_wrap_mode() const {
+ // BaseMaterial3D presents wrapping as a boolean property. Either the texture is repeated
+ // in both dimensions, non-mirrored, or it isn't repeated at all. This will cause oddities
+ // when people import models having other wrapping mode combinations.
+ return (wrap_s == WrapMode::REPEAT) && (wrap_t == WrapMode::REPEAT);
+ }
+
+ void set_wrap_mode(bool mat_repeats) {
+ if (mat_repeats) {
+ wrap_s = WrapMode::REPEAT;
+ wrap_t = WrapMode::REPEAT;
+ } else {
+ wrap_s = WrapMode::CLAMP_TO_EDGE;
+ wrap_t = WrapMode::CLAMP_TO_EDGE;
+ }
+ }
+
+protected:
+ static void _bind_methods();
+
+private:
+ FilterMode mag_filter = FilterMode::LINEAR;
+ FilterMode min_filter = FilterMode::LINEAR_MIPMAP_LINEAR;
+ WrapMode wrap_s = WrapMode::REPEAT;
+ WrapMode wrap_t = WrapMode::REPEAT;
+};
+
+VARIANT_ENUM_CAST(GLTFTextureSampler::FilterMode);
+VARIANT_ENUM_CAST(GLTFTextureSampler::WrapMode);
+
+#endif // GLTF_TEXTURE_SAMPLER_H
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h
index a64dc4a80b..6fd38d9445 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.h
+++ b/modules/gridmap/editor/grid_map_editor_plugin.h
@@ -241,7 +241,7 @@ protected:
void _notification(int p_what);
public:
- virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); }
+ virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); }
virtual String get_name() const override { return "GridMap"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index 05ce2b6147..de50e9ea1e 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -157,13 +157,13 @@ uint32_t GridMap::get_collision_mask() const {
void GridMap::set_collision_layer_value(int p_layer_number, bool p_value) {
ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
- uint32_t collision_layer = get_collision_layer();
+ uint32_t collision_layer_new = get_collision_layer();
if (p_value) {
- collision_layer |= 1 << (p_layer_number - 1);
+ collision_layer_new |= 1 << (p_layer_number - 1);
} else {
- collision_layer &= ~(1 << (p_layer_number - 1));
+ collision_layer_new &= ~(1 << (p_layer_number - 1));
}
- set_collision_layer(collision_layer);
+ set_collision_layer(collision_layer_new);
}
bool GridMap::get_collision_layer_value(int p_layer_number) const {
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 5b039e65c0..8785f327db 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -278,7 +278,7 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
return BAKE_OK;
}
-void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) {
+void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &p_probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) {
HashMap<Vertex, uint32_t, VertexHash> vertex_map;
//fill triangles array and vertex array
@@ -403,8 +403,8 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i
}
//also consider probe positions for bounds
- for (int i = 0; i < probe_positions.size(); i++) {
- Vector3 pp(probe_positions[i].position[0], probe_positions[i].position[1], probe_positions[i].position[2]);
+ for (int i = 0; i < p_probe_positions.size(); i++) {
+ Vector3 pp(p_probe_positions[i].position[0], p_probe_positions[i].position[1], p_probe_positions[i].position[2]);
bounds.expand_to(pp);
}
bounds.grow_by(0.1); //grow a bit to avoid numerical error
@@ -520,7 +520,7 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i
}
seams_buffer = rd->storage_buffer_create(sb.size(), sb);
- Vector<uint8_t> pb = probe_positions.to_byte_array();
+ Vector<uint8_t> pb = p_probe_positions.to_byte_array();
if (pb.size() == 0) {
pb.resize(sizeof(Probe));
}
diff --git a/modules/mono/.gitignore b/modules/mono/.gitignore
index fa6d00cbbb..2d62f9f88a 100644
--- a/modules/mono/.gitignore
+++ b/modules/mono/.gitignore
@@ -1,2 +1,5 @@
# Do not ignore solution files inside the mono module. Overrides Godot's global gitignore.
!*.sln
+
+# Generated by build_assemblies.py.
+SdkPackageVersions.props
diff --git a/modules/mono/Directory.Build.targets b/modules/mono/Directory.Build.targets
index 98410b93ae..e666b3ac9d 100644
--- a/modules/mono/Directory.Build.targets
+++ b/modules/mono/Directory.Build.targets
@@ -2,6 +2,8 @@
<PropertyGroup>
<_HasNuGetPackage Condition=" '$(_HasNuGetPackage)' == '' And '$(PackageId)' != '' And '$(GeneratePackageOnBuild.ToLower())' == 'true' ">true</_HasNuGetPackage>
<_HasNuGetPackage Condition=" '$(_HasNuGetPackage)' == '' ">false</_HasNuGetPackage>
+ <_HasSymbolsNuGetPackage Condition=" '$(_HasSymbolsNuGetPackage)' == '' And '$(PackageId)' != '' And '$(IncludeSymbols.ToLower())' == 'true' And '$(SymbolPackageFormat)' == 'snupkg' ">true</_HasSymbolsNuGetPackage>
+ <_HasSymbolsNuGetPackage Condition=" '$(_HasSymbolsNuGetPackage)' == '' ">false</_HasSymbolsNuGetPackage>
</PropertyGroup>
<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"
Condition=" '$(_HasNuGetPackage)' == 'true' ">
@@ -10,13 +12,15 @@
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
</PropertyGroup>
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
+ <Copy Condition=" '$(_HasSymbolsNuGetPackage)' == 'true' " SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).snupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
</Target>
<Target Name="PushNuGetPackagesToLocalSource" BeforeTargets="Pack"
Condition=" '$(_HasNuGetPackage)' == 'true' And '$(PushNuGetToLocalSource)' != '' ">
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(PushNuGetToLocalSource)\" />
+ <Copy Condition=" '$(_HasSymbolsNuGetPackage)' == 'true' " SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).snupkg" DestinationFolder="$(PushNuGetToLocalSource)\" />
</Target>
<Target Name="ClearNuGetLocalPackageCache" BeforeTargets="Pack"
Condition=" '$(_HasNuGetPackage)' == 'true' And '$(ClearNuGetLocalCache.ToLower())' == 'true' ">
- <RemoveDir Directories="$(NugetPackageRoot)/$(PackageId.ToLower())/$(PackageVersion)"/>
+ <RemoveDir Directories="$(NugetPackageRoot)/$(PackageId.ToLower())/$(PackageVersion)" />
</Target>
</Project>
diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props
deleted file mode 100644
index 65094aa34f..0000000000
--- a/modules/mono/SdkPackageVersions.props
+++ /dev/null
@@ -1,8 +0,0 @@
-<Project>
- <PropertyGroup>
- <PackageFloatingVersion_Godot>4.0.*-*</PackageFloatingVersion_Godot>
- <PackageVersion_GodotSharp>4.0.0-dev</PackageVersion_GodotSharp>
- <PackageVersion_Godot_NET_Sdk>4.0.0-dev8</PackageVersion_Godot_NET_Sdk>
- <PackageVersion_Godot_SourceGenerators>4.0.0-dev8</PackageVersion_Godot_SourceGenerators>
- </PropertyGroup>
-</Project>
diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py
index d28c3a0c3a..7343af0b39 100755
--- a/modules/mono/build_scripts/build_assemblies.py
+++ b/modules/mono/build_scripts/build_assemblies.py
@@ -256,7 +256,57 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, flo
return 0
+def generate_sdk_package_versions():
+ # I can't believe importing files in Python is so convoluted when not
+ # following the golden standard for packages/modules.
+ import os
+ import sys
+ from os.path import dirname
+
+ # We want ../../../methods.py.
+ script_path = dirname(os.path.abspath(__file__))
+ root_path = dirname(dirname(dirname(script_path)))
+
+ sys.path.insert(0, root_path)
+ from methods import get_version_info
+
+ version_info = get_version_info("")
+ sys.path.remove(root_path)
+
+ version_str = "{major}.{minor}.{patch}".format(**version_info)
+ version_status = version_info["status"]
+ if version_status != "stable": # Pre-release
+ # If version was overridden to be e.g. "beta3", we insert a dot between
+ # "beta" and "3" to follow SemVer 2.0.
+ import re
+
+ match = re.search(r"[\d]+$", version_status)
+ if match:
+ pos = match.start()
+ version_status = version_status[:pos] + "." + version_status[pos:]
+ version_str += "-" + version_status
+
+ props = """<Project>
+ <PropertyGroup>
+ <PackageVersion_GodotSharp>{0}</PackageVersion_GodotSharp>
+ <PackageVersion_Godot_NET_Sdk>{0}</PackageVersion_Godot_NET_Sdk>
+ <PackageVersion_Godot_SourceGenerators>{0}</PackageVersion_Godot_SourceGenerators>
+ </PropertyGroup>
+</Project>
+""".format(
+ version_str
+ )
+
+ # We write in ../SdkPackageVersions.props.
+ with open(os.path.join(dirname(script_path), "SdkPackageVersions.props"), "w") as f:
+ f.write(props)
+ f.close()
+
+
def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, float_size):
+ # Generate SdkPackageVersions.props
+ generate_sdk_package_versions()
+
# Godot API
exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, float_size)
if exit_code != 0:
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp
index c4547b4323..1f4b085bfb 100644
--- a/modules/mono/class_db_api_json.cpp
+++ b/modules/mono/class_db_api_json.cpp
@@ -227,8 +227,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
Ref<FileAccess> f = FileAccess::open(p_output_file, FileAccess::WRITE);
ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file '" + p_output_file + "'.");
- JSON json;
- f->store_string(json.stringify(classes_dict, "\t"));
+ f->store_string(JSON::stringify(classes_dict, "\t"));
print_line(String() + "ClassDB API JSON written to: " + ProjectSettings::get_singleton()->globalize_path(p_output_file));
}
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 97a1d5c8d8..345d2e4694 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -2398,9 +2398,9 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
if (EngineDebugger::is_active()) {
CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0,
"Script inherits from native type '" + String(native_name) +
- "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'");
+ "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'");
}
- ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'.");
+ ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'.");
}
Callable::CallError unchecked_error;
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index d469c28d4a..e5e53acb07 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -48,20 +48,10 @@ class CSharpScript;
class CSharpInstance;
class CSharpLanguage;
-#ifdef NO_SAFE_CAST
-template <typename TScriptInstance, typename TScriptLanguage>
-TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
- if (!p_inst) {
- return nullptr;
- }
- return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : nullptr;
-}
-#else
template <typename TScriptInstance, typename TScriptLanguage>
TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
return dynamic_cast<TScriptInstance *>(p_inst);
}
-#endif
#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst))
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 0d2bea2363..745a8b73f8 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -17,6 +17,8 @@ namespace GodotTools.Export
{
public partial class ExportPlugin : EditorExportPlugin
{
+ private List<string> _tempFolders = new List<string>();
+
public void RegisterExportSettings()
{
// TODO: These would be better as export preset options, but that doesn't seem to be supported yet
@@ -111,62 +113,78 @@ namespace GodotTools.Export
string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
- // TODO: This works for now, as we only implemented support for x86 family desktop so far, but it needs to be fixed
- string arch = features.Contains("x86_64") ? "x86_64" : "x86";
-
- string ridOS = DetermineRuntimeIdentifierOS(platform);
- string ridArch = DetermineRuntimeIdentifierArch(arch);
- string runtimeIdentifier = $"{ridOS}-{ridArch}";
-
- // Create temporary publish output directory
-
- string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
- $"{Process.GetCurrentProcess().Id}-{buildConfig}-{runtimeIdentifier}");
-
- if (!Directory.Exists(publishOutputTempDir))
- Directory.CreateDirectory(publishOutputTempDir);
-
- // Execute dotnet publish
-
- if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
- runtimeIdentifier, publishOutputTempDir))
+ var archs = new List<string>();
+ if (features.Contains("x86_64"))
{
- throw new InvalidOperationException("Failed to build project.");
+ archs.Add("x86_64");
}
-
- string soExt = ridOS switch
+ else if (features.Contains("x86_32"))
{
- OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
- OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib",
- _ => "so"
- };
-
- if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll"))
- // NativeAOT shared library output
- && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}")))
+ archs.Add("x86_32");
+ }
+ else if (features.Contains("arm64"))
{
- throw new NotSupportedException(
- "Publish succeeded but project assembly not found in the output directory");
+ archs.Add("arm64");
}
-
- // Copy all files from the dotnet publish output directory to
- // a data directory next to the Godot output executable.
-
- string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject());
-
- if (Directory.Exists(outputDataDir))
- Directory.Delete(outputDataDir, recursive: true); // Clean first
-
- Directory.CreateDirectory(outputDataDir);
-
- foreach (string dir in Directory.GetDirectories(publishOutputTempDir, "*", SearchOption.AllDirectories))
+ else if (features.Contains("universal"))
{
- Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(publishOutputTempDir.Length + 1)));
+ if (platform == OS.Platforms.MacOS)
+ {
+ archs.Add("x86_64");
+ archs.Add("arm64");
+ }
}
- foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
+ foreach (var arch in archs)
{
- File.Copy(file, Path.Combine(outputDataDir, file.Substring(publishOutputTempDir.Length + 1)));
+ string ridOS = DetermineRuntimeIdentifierOS(platform);
+ string ridArch = DetermineRuntimeIdentifierArch(arch);
+ string runtimeIdentifier = $"{ridOS}-{ridArch}";
+ string projectDataDirName = $"{DetermineDataDirNameForProject()}_{arch}";
+ if (platform == OS.Platforms.MacOS)
+ {
+ projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
+ }
+
+ // Create temporary publish output directory
+
+ string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
+ $"{Process.GetCurrentProcess().Id}-{buildConfig}-{runtimeIdentifier}");
+
+ _tempFolders.Add(publishOutputTempDir);
+
+ if (!Directory.Exists(publishOutputTempDir))
+ Directory.CreateDirectory(publishOutputTempDir);
+
+ // Execute dotnet publish
+
+ if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
+ runtimeIdentifier, publishOutputTempDir))
+ {
+ throw new InvalidOperationException("Failed to build project.");
+ }
+
+ string soExt = ridOS switch
+ {
+ OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
+ OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib",
+ _ => "so"
+ };
+
+ if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll"))
+ // NativeAOT shared library output
+ && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}")))
+ {
+ throw new NotSupportedException(
+ "Publish succeeded but project assembly not found in the output directory");
+ }
+
+ // Add to the exported project shared object list.
+
+ foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
+ {
+ AddSharedObject(file, tags: null, projectDataDirName);
+ }
}
}
@@ -198,6 +216,12 @@ namespace GodotTools.Export
if (Directory.Exists(aotTempDir))
Directory.Delete(aotTempDir, recursive: true);
+ foreach (string folder in _tempFolders)
+ {
+ Directory.Delete(folder, recursive: true);
+ }
+ _tempFolders.Clear();
+
// TODO: The following is just a workaround until the export plugins can be made to abort with errors
// We check for empty as well, because it's set to empty after hot-reloading
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 95a44d3b7e..d29e0d69ab 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -3326,11 +3326,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::PROJECTION: {
- Projection transform = p_val.operator Projection();
- if (transform == Projection()) {
+ Projection projection = p_val.operator Projection();
+ if (projection == Projection()) {
r_iarg.default_argument = "Projection.Identity";
} else {
- r_iarg.default_argument = "new Projection(new Vector4" + transform.matrix[0].operator String() + ", new Vector4" + transform.matrix[1].operator String() + ", new Vector4" + transform.matrix[2].operator String() + ", new Vector4" + transform.matrix[3].operator String() + ")";
+ r_iarg.default_argument = "new Projection(new Vector4" + projection.columns[0].operator String() + ", new Vector4" + projection.columns[1].operator String() + ", new Vector4" + projection.columns[2].operator String() + ", new Vector4" + projection.columns[3].operator String() + ")";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
index 3c75d18943..9b3969d453 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
@@ -76,6 +76,11 @@ namespace Godot
internal static bool TrySerializeDelegate(Delegate @delegate, Collections.Array serializedData)
{
+ if (@delegate is null)
+ {
+ return false;
+ }
+
if (@delegate is MulticastDelegate multicastDelegate)
{
bool someDelegatesSerialized = false;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 5827d3e591..a63b668387 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -27,6 +27,8 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+ <IncludeSymbols>true</IncludeSymbols>
+ <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<!-- SdkPackageVersions.props for easy access -->
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
index 5d69ad8ec6..8f623625fc 100644
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
@@ -22,6 +22,8 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+ <IncludeSymbols>true</IncludeSymbols>
+ <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);GODOT</DefineConstants>
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index c7e47d2718..185a7e60cf 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -94,138 +94,63 @@ String _get_mono_user_dir() {
class _GodotSharpDirs {
public:
- String res_data_dir;
String res_metadata_dir;
- String res_config_dir;
- String res_temp_dir;
- String res_temp_assemblies_base_dir;
String res_temp_assemblies_dir;
String mono_user_dir;
- String mono_logs_dir;
-
- String api_assemblies_base_dir;
String api_assemblies_dir;
#ifdef TOOLS_ENABLED
- String mono_solutions_dir;
String build_logs_dir;
-
String data_editor_tools_dir;
-#else
- // Equivalent of res_assemblies_dir, but in the data directory rather than in 'res://'.
- // Only defined on export templates. Used when exporting assemblies outside of PCKs.
- String data_game_assemblies_dir;
-#endif
-
- String data_mono_etc_dir;
- String data_mono_lib_dir;
-
-#ifdef WINDOWS_ENABLED
- String data_mono_bin_dir;
#endif
private:
_GodotSharpDirs() {
- res_data_dir = ProjectSettings::get_singleton()->get_project_data_path().path_join("mono");
+ String res_data_dir = ProjectSettings::get_singleton()->get_project_data_path().path_join("mono");
res_metadata_dir = res_data_dir.path_join("metadata");
- res_config_dir = res_data_dir.path_join("etc").path_join("mono");
// TODO use paths from csproj
- res_temp_dir = res_data_dir.path_join("temp");
- res_temp_assemblies_base_dir = res_temp_dir.path_join("bin");
- res_temp_assemblies_dir = res_temp_assemblies_base_dir.path_join(_get_expected_build_config());
-
- api_assemblies_base_dir = res_data_dir.path_join("assemblies");
+ res_temp_assemblies_dir = res_data_dir.path_join("temp").path_join("bin").path_join(_get_expected_build_config());
#ifdef WEB_ENABLED
mono_user_dir = "user://";
#else
mono_user_dir = _get_mono_user_dir();
#endif
- mono_logs_dir = mono_user_dir.path_join("mono_logs");
-
-#ifdef TOOLS_ENABLED
- mono_solutions_dir = mono_user_dir.path_join("solutions");
- build_logs_dir = mono_user_dir.path_join("build_logs");
-
- String base_path = ProjectSettings::get_singleton()->globalize_path("res://");
-#endif
String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir();
+ String res_dir = OS::get_singleton()->get_bundle_resource_dir();
#ifdef TOOLS_ENABLED
-
String data_dir_root = exe_dir.path_join("GodotSharp");
data_editor_tools_dir = data_dir_root.path_join("Tools");
- api_assemblies_base_dir = data_dir_root.path_join("Api");
-
- String data_mono_root_dir = data_dir_root.path_join("Mono");
- data_mono_etc_dir = data_mono_root_dir.path_join("etc");
-
-#ifdef ANDROID_ENABLED
- data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir();
-#else
- data_mono_lib_dir = data_mono_root_dir.path_join("lib");
-#endif
-
-#ifdef WINDOWS_ENABLED
- data_mono_bin_dir = data_mono_root_dir.path_join("bin");
-#endif
-
+ String api_assemblies_base_dir = data_dir_root.path_join("Api");
+ build_logs_dir = mono_user_dir.path_join("build_logs");
#ifdef MACOS_ENABLED
if (!DirAccess::exists(data_editor_tools_dir)) {
- data_editor_tools_dir = exe_dir.path_join("../Resources/GodotSharp/Tools");
+ data_editor_tools_dir = res_dir.path_join("GodotSharp").path_join("Tools");
}
-
if (!DirAccess::exists(api_assemblies_base_dir)) {
- api_assemblies_base_dir = exe_dir.path_join("../Resources/GodotSharp/Api");
- }
-
- if (!DirAccess::exists(data_mono_root_dir)) {
- data_mono_etc_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/etc");
- data_mono_lib_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/lib");
+ api_assemblies_base_dir = res_dir.path_join("GodotSharp").path_join("Api");
}
#endif
-
-#else
-
+ api_assemblies_dir = api_assemblies_base_dir.path_join(GDMono::get_expected_api_build_config());
+#else // TOOLS_ENABLED
+ String arch = Engine::get_singleton()->get_architecture_name();
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
- String data_dir_root = exe_dir.path_join("data_" + appname_safe);
+ String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch);
if (!DirAccess::exists(data_dir_root)) {
- data_dir_root = exe_dir.path_join("data_Godot");
+ data_dir_root = exe_dir.path_join("data_Godot_" + arch);
}
-
- String data_mono_root_dir = data_dir_root.path_join("Mono");
- data_mono_etc_dir = data_mono_root_dir.path_join("etc");
-
-#ifdef ANDROID_ENABLED
- data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir();
-#else
- data_mono_lib_dir = data_mono_root_dir.path_join("lib");
- data_game_assemblies_dir = data_dir_root.path_join("Assemblies");
-#endif
-
-#ifdef WINDOWS_ENABLED
- data_mono_bin_dir = data_mono_root_dir.path_join("bin");
-#endif
-
#ifdef MACOS_ENABLED
- if (!DirAccess::exists(data_mono_root_dir)) {
- data_mono_etc_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/etc");
- data_mono_lib_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/lib");
+ if (!DirAccess::exists(data_dir_root)) {
+ data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + arch);
}
-
- if (!DirAccess::exists(data_game_assemblies_dir)) {
- data_game_assemblies_dir = exe_dir.path_join("../Resources/GodotSharp/Assemblies");
+ if (!DirAccess::exists(data_dir_root)) {
+ data_dir_root = res_dir.path_join("data_Godot_" + arch);
}
#endif
-
-#endif
-
-#ifdef TOOLS_ENABLED
- api_assemblies_dir = api_assemblies_base_dir.path_join(GDMono::get_expected_api_build_config());
-#else
api_assemblies_dir = data_dir_root;
#endif
}
@@ -237,26 +162,10 @@ public:
}
};
-String get_res_data_dir() {
- return _GodotSharpDirs::get_singleton().res_data_dir;
-}
-
String get_res_metadata_dir() {
return _GodotSharpDirs::get_singleton().res_metadata_dir;
}
-String get_res_config_dir() {
- return _GodotSharpDirs::get_singleton().res_config_dir;
-}
-
-String get_res_temp_dir() {
- return _GodotSharpDirs::get_singleton().res_temp_dir;
-}
-
-String get_res_temp_assemblies_base_dir() {
- return _GodotSharpDirs::get_singleton().res_temp_assemblies_base_dir;
-}
-
String get_res_temp_assemblies_dir() {
return _GodotSharpDirs::get_singleton().res_temp_assemblies_dir;
}
@@ -265,23 +174,11 @@ String get_api_assemblies_dir() {
return _GodotSharpDirs::get_singleton().api_assemblies_dir;
}
-String get_api_assemblies_base_dir() {
- return _GodotSharpDirs::get_singleton().api_assemblies_base_dir;
-}
-
String get_mono_user_dir() {
return _GodotSharpDirs::get_singleton().mono_user_dir;
}
-String get_mono_logs_dir() {
- return _GodotSharpDirs::get_singleton().mono_logs_dir;
-}
-
#ifdef TOOLS_ENABLED
-String get_mono_solutions_dir() {
- return _GodotSharpDirs::get_singleton().mono_solutions_dir;
-}
-
String get_build_logs_dir() {
return _GodotSharpDirs::get_singleton().build_logs_dir;
}
@@ -289,23 +186,6 @@ String get_build_logs_dir() {
String get_data_editor_tools_dir() {
return _GodotSharpDirs::get_singleton().data_editor_tools_dir;
}
-#else
-String get_data_game_assemblies_dir() {
- return _GodotSharpDirs::get_singleton().data_game_assemblies_dir;
-}
#endif
-String get_data_mono_etc_dir() {
- return _GodotSharpDirs::get_singleton().data_mono_etc_dir;
-}
-
-String get_data_mono_lib_dir() {
- return _GodotSharpDirs::get_singleton().data_mono_lib_dir;
-}
-
-#ifdef WINDOWS_ENABLED
-String get_data_mono_bin_dir() {
- return _GodotSharpDirs::get_singleton().data_mono_bin_dir;
-}
-#endif
} // namespace GodotSharpDirs
diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h
index 03e62ffd82..cdfb8e4787 100644
--- a/modules/mono/godotsharp_dirs.h
+++ b/modules/mono/godotsharp_dirs.h
@@ -35,34 +35,18 @@
namespace GodotSharpDirs {
-String get_res_data_dir();
String get_res_metadata_dir();
-String get_res_config_dir();
-String get_res_temp_dir();
-String get_res_temp_assemblies_base_dir();
String get_res_temp_assemblies_dir();
String get_api_assemblies_dir();
-String get_api_assemblies_base_dir();
String get_mono_user_dir();
-String get_mono_logs_dir();
#ifdef TOOLS_ENABLED
-String get_mono_solutions_dir();
String get_build_logs_dir();
-
String get_data_editor_tools_dir();
-#else
-String get_data_game_assemblies_dir();
#endif
-String get_data_mono_etc_dir();
-String get_data_mono_lib_dir();
-
-#ifdef WINDOWS_ENABLED
-String get_data_mono_bin_dir();
-#endif
} // namespace GodotSharpDirs
#endif // GODOTSHARP_DIRS_H
diff --git a/modules/multiplayer/scene_replication_config.cpp b/modules/multiplayer/scene_replication_config.cpp
index ae06516b7b..61fe51b6c9 100644
--- a/modules/multiplayer/scene_replication_config.cpp
+++ b/modules/multiplayer/scene_replication_config.cpp
@@ -34,11 +34,11 @@
#include "scene/main/node.h"
bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_value) {
- String name = p_name;
+ String prop_name = p_name;
- if (name.begins_with("properties/")) {
- int idx = name.get_slicec('/', 1).to_int();
- String what = name.get_slicec('/', 2);
+ if (prop_name.begins_with("properties/")) {
+ int idx = prop_name.get_slicec('/', 1).to_int();
+ String what = prop_name.get_slicec('/', 2);
if (properties.size() == idx && what == "path") {
ERR_FAIL_COND_V(p_value.get_type() != Variant::NODE_PATH, false);
@@ -72,11 +72,11 @@ bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_val
}
bool SceneReplicationConfig::_get(const StringName &p_name, Variant &r_ret) const {
- String name = p_name;
+ String prop_name = p_name;
- if (name.begins_with("properties/")) {
- int idx = name.get_slicec('/', 1).to_int();
- String what = name.get_slicec('/', 2);
+ if (prop_name.begins_with("properties/")) {
+ int idx = prop_name.get_slicec('/', 1).to_int();
+ String what = prop_name.get_slicec('/', 2);
ERR_FAIL_INDEX_V(idx, properties.size(), false);
const ReplicationProperty &prop = properties[idx];
if (what == "path") {
diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp
index 101a66371b..23d60c4866 100644
--- a/modules/noise/noise_texture_2d.cpp
+++ b/modules/noise/noise_texture_2d.cpp
@@ -152,24 +152,24 @@ Ref<Image> NoiseTexture2D::_generate_texture() {
return Ref<Image>();
}
- Ref<Image> image;
+ Ref<Image> new_image;
if (seamless) {
- image = ref_noise->get_seamless_image(size.x, size.y, invert, in_3d_space, seamless_blend_skirt);
+ new_image = ref_noise->get_seamless_image(size.x, size.y, invert, in_3d_space, seamless_blend_skirt);
} else {
- image = ref_noise->get_image(size.x, size.y, invert, in_3d_space);
+ new_image = ref_noise->get_image(size.x, size.y, invert, in_3d_space);
}
if (color_ramp.is_valid()) {
- image = _modulate_with_gradient(image, color_ramp);
+ new_image = _modulate_with_gradient(new_image, color_ramp);
}
if (as_normal_map) {
- image->bump_map_to_normal_map(bump_strength);
+ new_image->bump_map_to_normal_map(bump_strength);
}
if (generate_mipmaps) {
- image->generate_mipmaps();
+ new_image->generate_mipmaps();
}
- return image;
+ return new_image;
}
Ref<Image> NoiseTexture2D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradient> p_gradient) {
@@ -206,8 +206,8 @@ void NoiseTexture2D::_update_texture() {
}
} else {
- Ref<Image> image = _generate_texture();
- _set_texture_image(image);
+ Ref<Image> new_image = _generate_texture();
+ _set_texture_image(new_image);
}
update_queued = false;
}
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index d4469b110b..5ac167ad98 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -92,6 +92,7 @@ if env["vulkan"]:
env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp")
+env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp")
diff --git a/modules/openxr/action_map/openxr_action.cpp b/modules/openxr/action_map/openxr_action.cpp
index 359975a480..0fb4f0773f 100644
--- a/modules/openxr/action_map/openxr_action.cpp
+++ b/modules/openxr/action_map/openxr_action.cpp
@@ -64,13 +64,13 @@ Ref<OpenXRAction> OpenXRAction::new_action(const char *p_name, const char *p_loc
}
String OpenXRAction::get_name_with_set() const {
- String name = get_name();
+ String action_name = get_name();
if (action_set != nullptr) {
- name = action_set->get_name() + "/" + name;
+ action_name = action_set->get_name() + "/" + action_name;
}
- return name;
+ return action_name;
}
void OpenXRAction::set_localized_name(const String p_localized_name) {
diff --git a/modules/websocket/websocket_macros.h b/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp
index b03bd8f45c..0f6aaf8afb 100644
--- a/modules/websocket/websocket_macros.h
+++ b/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* websocket_macros.h */
+/* openxr_composition_layer_depth_extension.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,39 +28,31 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef WEBSOCKET_MACROS_H
-#define WEBSOCKET_MACROS_H
+#include "openxr_composition_layer_depth_extension.h"
-// Defaults per peer buffers, 1024 packets with a shared 65536 bytes payload.
-#define DEF_PKT_SHIFT 10
-#define DEF_BUF_SHIFT 16
+OpenXRCompositionLayerDepthExtension *OpenXRCompositionLayerDepthExtension::singleton = nullptr;
-#define GDCICLASS(CNAME) \
-public: \
- static CNAME *(*_create)(); \
- \
- static Ref<CNAME> create_ref() { \
- if (!_create) \
- return Ref<CNAME>(); \
- return Ref<CNAME>(_create()); \
- } \
- \
- static CNAME *create() { \
- if (!_create) \
- return nullptr; \
- return _create(); \
- } \
- \
-protected:
+OpenXRCompositionLayerDepthExtension *OpenXRCompositionLayerDepthExtension::get_singleton() {
+ return singleton;
+}
-#define GDCINULL(CNAME) \
- CNAME *(*CNAME::_create)() = nullptr;
+OpenXRCompositionLayerDepthExtension::OpenXRCompositionLayerDepthExtension(OpenXRAPI *p_openxr_api) :
+ OpenXRExtensionWrapper(p_openxr_api) {
+ singleton = this;
-#define GDCIIMPL(IMPNAME, CNAME) \
-public: \
- static CNAME *_create() { return memnew(IMPNAME); } \
- static void make_default() { CNAME::_create = IMPNAME::_create; } \
- \
-protected:
+ request_extensions[XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME] = &available;
+}
-#endif // WEBSOCKET_MACROS_H
+OpenXRCompositionLayerDepthExtension::~OpenXRCompositionLayerDepthExtension() {
+ singleton = nullptr;
+}
+
+bool OpenXRCompositionLayerDepthExtension::is_available() {
+ return available;
+}
+
+XrCompositionLayerBaseHeader *OpenXRCompositionLayerDepthExtension::get_composition_layer() {
+ // Seems this is all done in our base layer... Just in case this changes...
+
+ return nullptr;
+}
diff --git a/modules/websocket/emws_client.h b/modules/openxr/extensions/openxr_composition_layer_depth_extension.h
index cdcec31e19..9533783d83 100644
--- a/modules/websocket/emws_client.h
+++ b/modules/openxr/extensions/openxr_composition_layer_depth_extension.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* emws_client.h */
+/* openxr_composition_layer_depth_extension.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,44 +28,26 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef EMWS_CLIENT_H
-#define EMWS_CLIENT_H
+#ifndef OPENXR_COMPOSITION_LAYER_DEPTH_EXTENSION_H
+#define OPENXR_COMPOSITION_LAYER_DEPTH_EXTENSION_H
-#ifdef WEB_ENABLED
+#include "openxr_composition_layer_provider.h"
+#include "openxr_extension_wrapper.h"
-#include "core/error/error_list.h"
-#include "emws_peer.h"
-#include "websocket_client.h"
+class OpenXRCompositionLayerDepthExtension : public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider {
+public:
+ static OpenXRCompositionLayerDepthExtension *get_singleton();
-class EMWSClient : public WebSocketClient {
- GDCIIMPL(EMWSClient, WebSocketClient);
+ OpenXRCompositionLayerDepthExtension(OpenXRAPI *p_openxr_api);
+ virtual ~OpenXRCompositionLayerDepthExtension() override;
-private:
- int _js_id = 0;
- bool _is_connecting = false;
- int _in_buf_size = DEF_BUF_SHIFT;
- int _in_pkt_size = DEF_PKT_SHIFT;
- int _out_buf_size = DEF_BUF_SHIFT;
+ bool is_available();
+ virtual XrCompositionLayerBaseHeader *get_composition_layer() override;
- static void _esws_on_connect(void *obj, char *proto);
- static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string);
- static void _esws_on_error(void *obj);
- static void _esws_on_close(void *obj, int code, const char *reason, int was_clean);
+private:
+ static OpenXRCompositionLayerDepthExtension *singleton;
-public:
- Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) override;
- Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()) override;
- Ref<WebSocketPeer> get_peer(int p_peer_id) const override;
- void disconnect_from_host(int p_code = 1000, String p_reason = "") override;
- IPAddress get_connected_host() const override;
- uint16_t get_connected_port() const override;
- virtual ConnectionStatus get_connection_status() const override;
- int get_max_packet_size() const override;
- virtual void poll() override;
- EMWSClient();
- ~EMWSClient();
+ bool available = false;
};
-#endif // WEB_ENABLED
-
-#endif // EMWS_CLIENT_H
+#endif // OPENXR_COMPOSITION_LAYER_DEPTH_EXTENSION_H
diff --git a/modules/openxr/extensions/openxr_composition_layer_provider.h b/modules/openxr/extensions/openxr_composition_layer_provider.h
index ba51389f7d..a4c4cbe0c6 100644
--- a/modules/openxr/extensions/openxr_composition_layer_provider.h
+++ b/modules/openxr/extensions/openxr_composition_layer_provider.h
@@ -31,6 +31,7 @@
#ifndef OPENXR_COMPOSITION_LAYER_PROVIDER_H
#define OPENXR_COMPOSITION_LAYER_PROVIDER_H
+#include "openxr_extension_wrapper.h"
#include <openxr/openxr.h>
// Interface for OpenXR extensions that provide a composition layer.
diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h
index 623c264e6e..c417c90d11 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper.h
@@ -100,11 +100,12 @@ public:
class OpenXRGraphicsExtensionWrapper : public OpenXRExtensionWrapper {
public:
virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) = 0;
+ virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) = 0;
virtual String get_swapchain_format_name(int64_t p_swapchain_format) const = 0;
virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) = 0;
virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) = 0;
virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) = 0;
- virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) = 0;
+ virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) = 0;
OpenXRGraphicsExtensionWrapper(OpenXRAPI *p_openxr_api) :
OpenXRExtensionWrapper(p_openxr_api){};
diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/openxr_vulkan_extension.cpp
index f9e771c934..ee5bda2881 100644
--- a/modules/openxr/extensions/openxr_vulkan_extension.cpp
+++ b/modules/openxr/extensions/openxr_vulkan_extension.cpp
@@ -228,6 +228,12 @@ void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usab
p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_UINT);
}
+void OpenXRVulkanExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) {
+ p_usable_swap_chains.push_back(VK_FORMAT_R32_SFLOAT);
+ p_usable_swap_chains.push_back(VK_FORMAT_D24_UNORM_S8_UINT);
+ p_usable_swap_chains.push_back(VK_FORMAT_D32_SFLOAT_S8_UINT);
+}
+
bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) {
XrSwapchainImageVulkanKHR *images = nullptr;
@@ -271,7 +277,7 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in
RenderingDevice::DataFormat format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB;
RenderingDevice::TextureSamples samples = RenderingDevice::TEXTURE_SAMPLES_1;
- uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
+ uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT;
switch (p_swapchain_format) {
case VK_FORMAT_R8G8B8A8_SRGB:
@@ -283,16 +289,32 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in
// will thus do an sRGB -> Linear conversion as expected.
// format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB;
format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UNORM;
+ usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
break;
case VK_FORMAT_B8G8R8A8_SRGB:
// format = RenderingDevice::DATA_FORMAT_B8G8R8A8_SRGB;
format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UNORM;
+ usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
break;
case VK_FORMAT_R8G8B8A8_UINT:
format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UINT;
+ usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
break;
case VK_FORMAT_B8G8R8A8_UINT:
format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UINT;
+ usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
+ break;
+ case VK_FORMAT_R32_SFLOAT:
+ format = RenderingDevice::DATA_FORMAT_R32_SFLOAT;
+ usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ break;
+ case VK_FORMAT_D24_UNORM_S8_UINT:
+ format = RenderingDevice::DATA_FORMAT_D24_UNORM_S8_UINT;
+ usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ break;
+ case VK_FORMAT_D32_SFLOAT_S8_UINT:
+ format = RenderingDevice::DATA_FORMAT_D32_SFLOAT_S8_UINT;
+ usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
break;
default:
// continue with our default value
@@ -328,8 +350,7 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in
break;
}
- Vector<RID> image_rids;
- Vector<RID> framebuffers;
+ Vector<RID> texture_rids;
// create Godot texture objects for each entry in our swapchain
for (uint64_t i = 0; i < swapchain_length; i++) {
@@ -344,19 +365,10 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in
1,
p_array_size);
- image_rids.push_back(image_rid);
-
- {
- Vector<RID> fb;
- fb.push_back(image_rid);
-
- RID fb_rid = rendering_device->framebuffer_create(fb, RenderingDevice::INVALID_ID, p_array_size);
- framebuffers.push_back(fb_rid);
- }
+ texture_rids.push_back(image_rid);
}
- data->image_rids = image_rids;
- data->framebuffers = framebuffers;
+ data->texture_rids = texture_rids;
memfree(images);
@@ -370,33 +382,19 @@ bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 4; i++) {
- r_camera_matrix.matrix[j][i] = matrix.m[j * 4 + i];
+ r_camera_matrix.columns[j][i] = matrix.m[j * 4 + i];
}
}
return true;
}
-bool OpenXRVulkanExtension::copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) {
+RID OpenXRVulkanExtension::get_texture(void *p_swapchain_graphics_data, int p_image_index) {
SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data;
- ERR_FAIL_NULL_V(data, false);
- ERR_FAIL_COND_V(p_from_render_target.is_null(), false);
+ ERR_FAIL_NULL_V(data, RID());
- RID source_image = RendererRD::TextureStorage::get_singleton()->render_target_get_rd_texture(p_from_render_target);
- ERR_FAIL_COND_V(source_image.is_null(), false);
-
- RID depth_image; // TODO implement
-
- ERR_FAIL_INDEX_V(p_image_index, data->framebuffers.size(), false);
- RID fb = data->framebuffers[p_image_index];
- ERR_FAIL_COND_V(fb.is_null(), false);
-
- // Our vulkan extension can only be used in conjunction with our vulkan renderer.
- RendererRD::CopyEffects *copy_effects = RendererRD::CopyEffects::get_singleton();
- ERR_FAIL_NULL_V(copy_effects, false);
- copy_effects->copy_to_fb_rect(source_image, fb, Rect2i(), false, false, false, false, depth_image, data->is_multiview);
-
- return true;
+ ERR_FAIL_INDEX_V(p_image_index, data->texture_rids.size(), RID());
+ return data->texture_rids[p_image_index];
}
void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) {
@@ -411,17 +409,11 @@ void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_g
RenderingDevice *rendering_device = rendering_server->get_rendering_device();
ERR_FAIL_NULL(rendering_device);
- for (int i = 0; i < data->image_rids.size(); i++) {
- // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain
- rendering_device->free(data->image_rids[i]);
- }
- data->image_rids.clear();
-
- for (int i = 0; i < data->framebuffers.size(); i++) {
+ for (int i = 0; i < data->texture_rids.size(); i++) {
// This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain
- rendering_device->free(data->framebuffers[i]);
+ rendering_device->free(data->texture_rids[i]);
}
- data->framebuffers.clear();
+ data->texture_rids.clear();
memdelete(data);
*p_swapchain_graphics_data = nullptr;
diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h
index d6e9917900..71abe3b166 100644
--- a/modules/openxr/extensions/openxr_vulkan_extension.h
+++ b/modules/openxr/extensions/openxr_vulkan_extension.h
@@ -69,11 +69,12 @@ public:
virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) override;
virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override;
+ virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override;
virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override;
virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) override;
virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override;
virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) override;
- virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) override;
+ virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) override;
private:
static OpenXRVulkanExtension *singleton;
@@ -81,8 +82,7 @@ private:
struct SwapchainGraphicsData {
bool is_multiview;
- Vector<RID> image_rids;
- Vector<RID> framebuffers;
+ Vector<RID> texture_rids;
};
bool check_graphics_api_support(XrVersion p_desired_version);
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 16879ac4e5..8a67462613 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -49,6 +49,7 @@
#include "extensions/openxr_vulkan_extension.h"
#endif
+#include "extensions/openxr_composition_layer_depth_extension.h"
#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
#include "extensions/openxr_hand_tracking_extension.h"
#include "extensions/openxr_htc_vive_tracker_extension.h"
@@ -130,11 +131,9 @@ bool OpenXRAPI::load_layer_properties() {
result = xrEnumerateApiLayerProperties(num_layer_properties, &num_layer_properties, layer_properties);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate api layer properties");
-#ifdef DEBUG
for (uint32_t i = 0; i < num_layer_properties; i++) {
- print_line("OpenXR: Found OpenXR layer ", layer_properties[i].layerName);
+ print_verbose(String("OpenXR: Found OpenXR layer ") + layer_properties[i].layerName);
}
-#endif
return true;
}
@@ -162,11 +161,9 @@ bool OpenXRAPI::load_supported_extensions() {
result = xrEnumerateInstanceExtensionProperties(nullptr, num_supported_extensions, &num_supported_extensions, supported_extensions);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate extension properties");
-#ifdef DEBUG
for (uint32_t i = 0; i < num_supported_extensions; i++) {
- print_line("OpenXR: Found OpenXR extension ", supported_extensions[i].extensionName);
+ print_verbose(String("OpenXR: Found OpenXR extension ") + supported_extensions[i].extensionName);
}
-#endif
return true;
}
@@ -174,17 +171,10 @@ bool OpenXRAPI::load_supported_extensions() {
bool OpenXRAPI::is_extension_supported(const String &p_extension) const {
for (uint32_t i = 0; i < num_supported_extensions; i++) {
if (supported_extensions[i].extensionName == p_extension) {
-#ifdef DEBUG
- print_line("OpenXR: requested extension", p_extension, "is supported");
-#endif
return true;
}
}
-#ifdef DEBUG
- print_line("OpenXR: requested extension", p_extension, "is not supported");
-#endif
-
return false;
}
@@ -244,7 +234,7 @@ bool OpenXRAPI::create_instance() {
if (!is_extension_supported(requested_extension.key)) {
if (requested_extension.value == nullptr) {
// nullptr means this is a manditory extension so we fail
- ERR_FAIL_V_MSG(false, "OpenXR: OpenXR Runtime does not support OpenGL extension!");
+ ERR_FAIL_V_MSG(false, String("OpenXR: OpenXR Runtime does not support ") + requested_extension.key + String(" extension!"));
} else {
// set this extension as not supported
*requested_extension.value = false;
@@ -400,11 +390,9 @@ bool OpenXRAPI::load_supported_view_configuration_types() {
result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerateview configurations");
-#ifdef DEBUG
for (uint32_t i = 0; i < num_view_configuration_types; i++) {
- print_line("OpenXR: Found supported view configuration ", OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i]));
+ print_verbose(String("OpenXR: Found supported view configuration ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i]));
}
-#endif
return true;
}
@@ -453,17 +441,15 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType
result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, view_count, &view_count, view_configuration_views);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate view configurations");
-#ifdef DEBUG
for (uint32_t i = 0; i < view_count; i++) {
- print_line("OpenXR: Found supported view configuration view");
- print_line(" - width: ", view_configuration_views[i].maxImageRectWidth);
- print_line(" - height: ", view_configuration_views[i].maxImageRectHeight);
- print_line(" - sample count: ", view_configuration_views[i].maxSwapchainSampleCount);
- print_line(" - recommended render width: ", view_configuration_views[i].recommendedImageRectWidth);
- print_line(" - recommended render height: ", view_configuration_views[i].recommendedImageRectHeight);
- print_line(" - recommended render sample count: ", view_configuration_views[i].recommendedSwapchainSampleCount);
+ print_verbose("OpenXR: Found supported view configuration view");
+ print_verbose(String(" - width: ") + view_configuration_views[i].maxImageRectWidth);
+ print_verbose(String(" - height: ") + view_configuration_views[i].maxImageRectHeight);
+ print_verbose(String(" - sample count: ") + view_configuration_views[i].maxSwapchainSampleCount);
+ print_verbose(String(" - recommended render width: ") + view_configuration_views[i].recommendedImageRectWidth);
+ print_verbose(String(" - recommended render height: ") + view_configuration_views[i].recommendedImageRectHeight);
+ print_verbose(String(" - recommended render sample count: ") + view_configuration_views[i].recommendedSwapchainSampleCount);
}
-#endif
return true;
}
@@ -545,11 +531,9 @@ bool OpenXRAPI::load_supported_reference_spaces() {
result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate reference spaces");
- // #ifdef DEBUG
for (uint32_t i = 0; i < num_reference_spaces; i++) {
- print_line("OpenXR: Found supported reference space ", OpenXRUtil::get_reference_space_name(supported_reference_spaces[i]));
+ print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i]));
}
- // #endif
return true;
}
@@ -642,11 +626,9 @@ bool OpenXRAPI::load_supported_swapchain_formats() {
result = xrEnumerateSwapchainFormats(session, num_swapchain_formats, &num_swapchain_formats, supported_swapchain_formats);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate swapchain formats");
- // #ifdef DEBUG
for (uint32_t i = 0; i < num_swapchain_formats; i++) {
- print_line("OpenXR: Found supported swapchain format ", get_swapchain_format_name(supported_swapchain_formats[i]));
+ print_verbose(String("OpenXR: Found supported swapchain format ") + get_swapchain_format_name(supported_swapchain_formats[i]));
}
- // #endif
return true;
}
@@ -663,7 +645,7 @@ bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) {
return false;
}
-bool OpenXRAPI::create_main_swapchain() {
+bool OpenXRAPI::create_swapchains() {
ERR_FAIL_NULL_V(graphics_extension, false);
ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
@@ -681,34 +663,36 @@ bool OpenXRAPI::create_main_swapchain() {
already rendering the next frame.
Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create,
- as we render 3D content into internal buffers that are copied into the swapchain, we don't get any of the performance gains
- until such time as we implement VRS.
+ as we render 3D content into internal buffers that are copied into the swapchain, we do now have (basic) VRS support
*/
- // Build a vector with swapchain formats we want to use, from best fit to worst
- Vector<int64_t> usable_swapchain_formats;
- int64_t swapchain_format_to_use = 0;
+ Size2 recommended_size = get_recommended_target_size();
- graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats);
+ // We start with our color swapchain...
+ {
+ // Build a vector with swapchain formats we want to use, from best fit to worst
+ Vector<int64_t> usable_swapchain_formats;
+ int64_t swapchain_format_to_use = 0;
- // now find out which one is supported
- for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) {
- if (is_swapchain_format_supported(usable_swapchain_formats[i])) {
- swapchain_format_to_use = usable_swapchain_formats[i];
- }
- }
+ graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats);
- if (swapchain_format_to_use == 0) {
- swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best...
- print_line("Couldn't find usable swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead.");
- } else {
- print_line("Using swap chain format:", get_swapchain_format_name(swapchain_format_to_use));
- }
+ // now find out which one is supported
+ for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) {
+ if (is_swapchain_format_supported(usable_swapchain_formats[i])) {
+ swapchain_format_to_use = usable_swapchain_formats[i];
+ }
+ }
- Size2 recommended_size = get_recommended_target_size();
+ if (swapchain_format_to_use == 0) {
+ swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best...
+ print_line("Couldn't find usable color swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead.");
+ } else {
+ print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(swapchain_format_to_use));
+ }
- if (!create_swapchain(swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchain, &swapchain_graphics_data)) {
- return false;
+ if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) {
+ return false;
+ }
}
views = (XrView *)memalloc(sizeof(XrView) * view_count);
@@ -717,18 +701,73 @@ bool OpenXRAPI::create_main_swapchain() {
projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count);
ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views");
+ // We create our depth swapchain if:
+ // - we support our depth layer extension
+ // - we have our spacewarp extension (not yet implemented)
+ if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) {
+ // Build a vector with swapchain formats we want to use, from best fit to worst
+ Vector<int64_t> usable_swapchain_formats;
+ int64_t swapchain_format_to_use = 0;
+
+ graphics_extension->get_usable_depth_formats(usable_swapchain_formats);
+
+ // now find out which one is supported
+ for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) {
+ if (is_swapchain_format_supported(usable_swapchain_formats[i])) {
+ swapchain_format_to_use = usable_swapchain_formats[i];
+ }
+ }
+
+ if (swapchain_format_to_use == 0) {
+ swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best...
+ print_line("Couldn't find usable depth swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead.");
+ } else {
+ print_verbose(String("Using depth swap chain format:") + get_swapchain_format_name(swapchain_format_to_use));
+ }
+
+ if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) {
+ return false;
+ }
+
+ depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count);
+ ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views");
+ }
+
+ // We create our velocity swapchain if:
+ // - we have our spacewarp extension (not yet implemented)
+ {
+ // TBD
+ }
+
for (uint32_t i = 0; i < view_count; i++) {
views[i].type = XR_TYPE_VIEW;
views[i].next = nullptr;
projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
projection_views[i].next = nullptr;
- projection_views[i].subImage.swapchain = swapchain;
+ projection_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain;
projection_views[i].subImage.imageArrayIndex = i;
projection_views[i].subImage.imageRect.offset.x = 0;
projection_views[i].subImage.imageRect.offset.y = 0;
projection_views[i].subImage.imageRect.extent.width = recommended_size.width;
projection_views[i].subImage.imageRect.extent.height = recommended_size.height;
+
+ if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) {
+ projection_views[i].next = &depth_views[i];
+
+ depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR;
+ depth_views[i].next = nullptr;
+ depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain;
+ depth_views[i].subImage.imageArrayIndex = 0;
+ depth_views[i].subImage.imageRect.offset.x = 0;
+ depth_views[i].subImage.imageRect.offset.y = 0;
+ depth_views[i].subImage.imageRect.extent.width = recommended_size.width;
+ depth_views[i].subImage.imageRect.extent.height = recommended_size.height;
+ depth_views[i].minDepth = 0.0;
+ depth_views[i].maxDepth = 1.0;
+ depth_views[i].nearZ = 0.01; // Near and far Z will be set to the correct values in fill_projection_matrix
+ depth_views[i].farZ = 100.0;
+ }
};
return true;
@@ -740,7 +779,7 @@ void OpenXRAPI::destroy_session() {
}
if (graphics_extension) {
- graphics_extension->cleanup_swapchain_graphics_data(&swapchain_graphics_data);
+ graphics_extension->cleanup_swapchain_graphics_data(&swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data);
}
if (views != nullptr) {
@@ -753,9 +792,16 @@ void OpenXRAPI::destroy_session() {
projection_views = nullptr;
}
- if (swapchain != XR_NULL_HANDLE) {
- xrDestroySwapchain(swapchain);
- swapchain = XR_NULL_HANDLE;
+ if (depth_views != nullptr) {
+ memfree(depth_views);
+ depth_views = nullptr;
+ }
+
+ for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
+ if (swapchains[i].swapchain != XR_NULL_HANDLE) {
+ xrDestroySwapchain(swapchains[i].swapchain);
+ swapchains[i].swapchain = XR_NULL_HANDLE;
+ }
}
if (supported_swapchain_formats != nullptr) {
@@ -789,7 +835,7 @@ void OpenXRAPI::destroy_session() {
}
}
-bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) {
+bool OpenXRAPI::create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) {
ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
ERR_FAIL_NULL_V(graphics_extension, false);
@@ -807,7 +853,7 @@ bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, u
XR_TYPE_SWAPCHAIN_CREATE_INFO, // type
next_pointer, // next
0, // createFlags
- XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, // usageFlags
+ p_usage_flags, // usageFlags
p_swapchain_format, // format
p_sample_count, // sampleCount
p_width, // width
@@ -835,9 +881,7 @@ bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, u
}
bool OpenXRAPI::on_state_idle() {
-#ifdef DEBUG
- print_line("On state idle");
-#endif
+ print_verbose("On state idle");
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_state_idle();
@@ -847,9 +891,7 @@ bool OpenXRAPI::on_state_idle() {
}
bool OpenXRAPI::on_state_ready() {
-#ifdef DEBUG
- print_line("On state ready");
-#endif
+ print_verbose("On state ready");
// begin session
XrSessionBeginInfo session_begin_info = {
@@ -871,7 +913,7 @@ bool OpenXRAPI::on_state_ready() {
// That will be very very ugly
// The other possibility is to create a separate OpenXRViewport type specifically for this goal as part of our OpenXR module
- if (!create_main_swapchain()) {
+ if (!create_swapchains()) {
return false;
}
@@ -892,9 +934,7 @@ bool OpenXRAPI::on_state_ready() {
}
bool OpenXRAPI::on_state_synchronized() {
-#ifdef DEBUG
- print_line("On state synchronized");
-#endif
+ print_verbose("On state synchronized");
// Just in case, see if we already have active trackers...
List<RID> trackers;
@@ -911,9 +951,7 @@ bool OpenXRAPI::on_state_synchronized() {
}
bool OpenXRAPI::on_state_visible() {
-#ifdef DEBUG
- print_line("On state visible");
-#endif
+ print_verbose("On state visible");
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_state_visible();
@@ -927,9 +965,7 @@ bool OpenXRAPI::on_state_visible() {
}
bool OpenXRAPI::on_state_focused() {
-#ifdef DEBUG
- print_line("On state focused");
-#endif
+ print_verbose("On state focused");
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_state_focused();
@@ -943,9 +979,7 @@ bool OpenXRAPI::on_state_focused() {
}
bool OpenXRAPI::on_state_stopping() {
-#ifdef DEBUG
- print_line("On state stopping");
-#endif
+ print_verbose("On state stopping");
if (xr_interface) {
xr_interface->on_state_stopping();
@@ -971,9 +1005,7 @@ bool OpenXRAPI::on_state_stopping() {
}
bool OpenXRAPI::on_state_loss_pending() {
-#ifdef DEBUG
- print_line("On state loss pending");
-#endif
+ print_verbose("On state loss pending");
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_state_loss_pending();
@@ -985,9 +1017,7 @@ bool OpenXRAPI::on_state_loss_pending() {
}
bool OpenXRAPI::on_state_exiting() {
-#ifdef DEBUG
- print_line("On state existing");
-#endif
+ print_verbose("On state existing");
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_state_exiting();
@@ -1259,12 +1289,10 @@ XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform,
head_pose_confidence = confidence;
if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) {
print_line("OpenXR head space location not valid (check tracking?)");
-#ifdef DEBUG
} else if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_LOW) {
- print_line("OpenVR Head pose now tracking with low confidence");
+ print_verbose("OpenVR Head pose now tracking with low confidence");
} else {
- print_line("OpenVR Head pose now tracking with high confidence");
-#endif
+ print_verbose("OpenVR Head pose now tracking with high confidence");
}
}
@@ -1304,6 +1332,15 @@ bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z
return false;
}
+ // if we're using depth views, make sure we update our near and far there...
+ if (depth_views != nullptr) {
+ for (uint32_t i = 0; i < view_count; i++) {
+ depth_views[i].nearZ = p_z_near;
+ depth_views[i].farZ = p_z_far;
+ }
+ }
+
+ // now update our projection
return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix);
}
@@ -1343,7 +1380,7 @@ bool OpenXRAPI::poll_events() {
// TODO We get this event if we're about to loose our OpenXR instance.
// We should queue exiting Godot at this point.
- print_verbose("OpenXR EVENT: instance loss pending at " + itos(event->lossTime));
+ print_verbose(String("OpenXR EVENT: instance loss pending at ") + itos(event->lossTime));
return false;
} break;
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
@@ -1351,9 +1388,9 @@ bool OpenXRAPI::poll_events() {
session_state = event->state;
if (session_state >= XR_SESSION_STATE_MAX_ENUM) {
- print_verbose("OpenXR EVENT: session state changed to UNKNOWN - " + itos(session_state));
+ print_verbose(String("OpenXR EVENT: session state changed to UNKNOWN - ") + itos(session_state));
} else {
- print_verbose("OpenXR EVENT: session state changed to " + OpenXRUtil::get_session_state_name(session_state));
+ print_verbose(String("OpenXR EVENT: session state changed to ") + OpenXRUtil::get_session_state_name(session_state));
switch (session_state) {
case XR_SESSION_STATE_IDLE:
@@ -1388,7 +1425,7 @@ bool OpenXRAPI::poll_events() {
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: {
XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent;
- print_verbose("OpenXR EVENT: reference space type " + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!");
+ print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!");
if (event->poseValid && xr_interface) {
xr_interface->on_pose_recentered();
}
@@ -1407,7 +1444,7 @@ bool OpenXRAPI::poll_events() {
} break;
default:
if (!handled) {
- print_verbose("OpenXR Unhandled event type " + OpenXRUtil::get_structure_type_name(runtimeEvent.type));
+ print_verbose(String("OpenXR Unhandled event type ") + OpenXRUtil::get_structure_type_name(runtimeEvent.type));
}
break;
}
@@ -1442,15 +1479,15 @@ bool OpenXRAPI::process() {
return true;
}
-bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) {
- ERR_FAIL_COND_V(image_acquired, true); // this was not released when it should be, error out and re-use...
+bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) {
+ ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // this was not released when it should be, error out and re-use...
XrResult result;
XrSwapchainImageAcquireInfo swapchain_image_acquire_info = {
XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type
nullptr // next
};
- result = xrAcquireSwapchainImage(p_swapchain, &swapchain_image_acquire_info, &r_image_index);
+ result = xrAcquireSwapchainImage(p_swapchain.swapchain, &swapchain_image_acquire_info, &p_swapchain.image_index);
if (XR_FAILED(result)) {
print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]");
return false;
@@ -1462,7 +1499,7 @@ bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index)
17000000 // timeout in nanoseconds
};
- result = xrWaitSwapchainImage(p_swapchain, &swapchain_image_wait_info);
+ result = xrWaitSwapchainImage(p_swapchain.swapchain, &swapchain_image_wait_info);
if (XR_FAILED(result)) {
print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]");
return false;
@@ -1471,12 +1508,12 @@ bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index)
return true;
}
-bool OpenXRAPI::release_image(XrSwapchain p_swapchain) {
+bool OpenXRAPI::release_image(OpenXRSwapChainInfo &p_swapchain) {
XrSwapchainImageReleaseInfo swapchain_image_release_info = {
XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type
nullptr // next
};
- XrResult result = xrReleaseSwapchainImage(swapchain, &swapchain_image_release_info);
+ XrResult result = xrReleaseSwapchainImage(p_swapchain.swapchain, &swapchain_image_release_info);
if (XR_FAILED(result)) {
print_line("OpenXR: failed to release swapchain image! [", get_error_string(result), "]");
return false;
@@ -1515,7 +1552,7 @@ void OpenXRAPI::pre_render() {
if (frame_state.predictedDisplayPeriod > 500000000) {
// display period more then 0.5 seconds? must be wrong data
- print_verbose("OpenXR resetting invalid display period " + rtos(frame_state.predictedDisplayPeriod));
+ print_verbose(String("OpenXR resetting invalid display period ") + rtos(frame_state.predictedDisplayPeriod));
frame_state.predictedDisplayPeriod = 0;
}
@@ -1562,13 +1599,11 @@ void OpenXRAPI::pre_render() {
}
if (view_pose_valid != pose_valid) {
view_pose_valid = pose_valid;
-#ifdef DEBUG
if (!view_pose_valid) {
- print_line("OpenXR View pose became invalid");
+ print_verbose("OpenXR View pose became invalid");
} else {
- print_line("OpenXR View pose became valid");
+ print_verbose("OpenXR View pose became valid");
}
-#endif
}
// let's start our frame..
@@ -1590,28 +1625,41 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) {
// TODO: at some point in time we may support multiple viewports in which case we need to handle that...
+ // Acquire our images
+ for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
+ if (!swapchains[i].image_acquired && swapchains[i].swapchain != XR_NULL_HANDLE) {
+ if (!acquire_image(swapchains[i])) {
+ return false;
+ }
+ swapchains[i].image_acquired = true;
+ }
+ }
+
return true;
}
+RID OpenXRAPI::get_color_texture() {
+ if (swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) {
+ return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_COLOR].image_index);
+ } else {
+ return RID();
+ }
+}
+
+RID OpenXRAPI::get_depth_texture() {
+ if (swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) {
+ return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_DEPTH].image_index);
+ } else {
+ return RID();
+ }
+}
+
void OpenXRAPI::post_draw_viewport(RID p_render_target) {
if (!can_render()) {
return;
}
- // TODO: at some point in time we may support multiple viewports in which case we need to handle that...
-
- // TODO: if we can get PR 51179 to work properly we can change away from this approach and move this into get_external_texture or something
- if (!image_acquired) {
- if (!acquire_image(swapchain, image_index)) {
- return;
- }
- image_acquired = true;
-
- // print_line("OpenXR: acquired image " + itos(image_index) + ", copying...");
-
- // Copy our buffer into our swap chain (remove once PR 51179 is done)
- graphics_extension->copy_render_target_to_image(p_render_target, swapchain_graphics_data, image_index);
- }
+ // Nothing to do here at this point in time...
};
void OpenXRAPI::end_frame() {
@@ -1623,7 +1671,7 @@ void OpenXRAPI::end_frame() {
return;
}
- if (frame_state.shouldRender && view_pose_valid && !image_acquired) {
+ if (frame_state.shouldRender && view_pose_valid && !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) {
print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!");
}
@@ -1631,7 +1679,7 @@ void OpenXRAPI::end_frame() {
// - shouldRender set to true
// - a valid view pose for projection_views[eye].pose to submit layer
// - an image to render
- if (!frame_state.shouldRender || !view_pose_valid || !image_acquired) {
+ if (!frame_state.shouldRender || !view_pose_valid || !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) {
// submit 0 layers when we shouldn't render
XrFrameEndInfo frame_end_info = {
XR_TYPE_FRAME_END_INFO, // type
@@ -1652,10 +1700,12 @@ void OpenXRAPI::end_frame() {
}
// release our swapchain image if we acquired it
- if (image_acquired) {
- image_acquired = false; // whether we succeed or not, consider this released.
+ for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
+ if (swapchains[i].image_acquired) {
+ swapchains[i].image_acquired = false; // whether we succeed or not, consider this released.
- release_image(swapchain);
+ release_image(swapchains[i]);
+ }
}
for (uint32_t eye = 0; eye < view_count; eye++) {
@@ -1763,6 +1813,7 @@ OpenXRAPI::OpenXRAPI() {
// register our other extensions
register_extension_wrapper(memnew(OpenXRPalmPoseExtension(this)));
+ register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension(this)));
register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension(this)));
register_extension_wrapper(memnew(OpenXRHandTrackingExtension(this)));
register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper(this)));
@@ -1991,8 +2042,6 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n
copy_string_to_char_buffer(p_name, action_set_info.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE);
copy_string_to_char_buffer(p_localized_name, action_set_info.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE);
- // print_line("Creating action set ", action_set_info.actionSetName, " - ", action_set_info.localizedActionSetName, " (", itos(action_set_info.priority), ")");
-
XrResult result = xrCreateActionSet(instance, &action_set_info, &action_set.handle);
if (XR_FAILED(result)) {
print_line("OpenXR: failed to create action set ", p_name, "! [", get_error_string(result), "]");
@@ -2146,8 +2195,6 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String
copy_string_to_char_buffer(p_name, action_info.actionName, XR_MAX_ACTION_NAME_SIZE);
copy_string_to_char_buffer(p_localized_name, action_info.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE);
- // print_line("Creating action ", action_info.actionName, action_info.localizedActionName, action_info.countSubactionPaths);
-
XrResult result = xrCreateAction(action_set->handle, &action_info, &action.handle);
if (XR_FAILED(result)) {
print_line("OpenXR: failed to create action ", p_name, "! [", get_error_string(result), "]");
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 316886239e..bd69432dcb 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -120,15 +120,28 @@ private:
OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr;
XrSystemGraphicsProperties graphics_properties;
- void *swapchain_graphics_data = nullptr;
- uint32_t image_index = 0;
- bool image_acquired = false;
uint32_t view_count = 0;
XrViewConfigurationView *view_configuration_views = nullptr;
XrView *views = nullptr;
XrCompositionLayerProjectionView *projection_views = nullptr;
- XrSwapchain swapchain = XR_NULL_HANDLE;
+ XrCompositionLayerDepthInfoKHR *depth_views = nullptr; // Only used by Composition Layer Depth Extension if available
+
+ enum OpenXRSwapChainTypes {
+ OPENXR_SWAPCHAIN_COLOR,
+ OPENXR_SWAPCHAIN_DEPTH,
+ // OPENXR_SWAPCHAIN_VELOCITY,
+ OPENXR_SWAPCHAIN_MAX
+ };
+
+ struct OpenXRSwapChainInfo {
+ XrSwapchain swapchain = XR_NULL_HANDLE;
+ void *swapchain_graphics_data = nullptr;
+ uint32_t image_index = 0;
+ bool image_acquired = false;
+ };
+
+ OpenXRSwapChainInfo swapchains[OPENXR_SWAPCHAIN_MAX];
XrSpace play_space = XR_NULL_HANDLE;
XrSpace view_space = XR_NULL_HANDLE;
@@ -212,13 +225,13 @@ private:
bool setup_spaces();
bool load_supported_swapchain_formats();
bool is_swapchain_format_supported(int64_t p_swapchain_format);
- bool create_main_swapchain();
+ bool create_swapchains();
void destroy_session();
// swapchains
- bool create_swapchain(int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data);
- bool acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index);
- bool release_image(XrSwapchain p_swapchain);
+ bool create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data);
+ bool acquire_image(OpenXRSwapChainInfo &p_swapchain);
+ bool release_image(OpenXRSwapChainInfo &p_swapchain);
// action map
struct Tracker { // Trackers represent tracked physical objects such as controllers, pucks, etc.
@@ -318,6 +331,8 @@ public:
void pre_render();
bool pre_draw_viewport(RID p_render_target);
+ RID get_color_texture();
+ RID get_depth_texture();
void post_draw_viewport(RID p_render_target);
void end_frame();
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 01e148e00f..68414ae84e 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -132,10 +132,10 @@ void OpenXRInterface::_load_action_map() {
if (action_map.is_valid()) {
HashMap<Ref<OpenXRAction>, Action *> xr_actions;
- Array action_sets = action_map->get_action_sets();
- for (int i = 0; i < action_sets.size(); i++) {
+ Array action_set_array = action_map->get_action_sets();
+ for (int i = 0; i < action_set_array.size(); i++) {
// Create our action set
- Ref<OpenXRActionSet> xr_action_set = action_sets[i];
+ Ref<OpenXRActionSet> xr_action_set = action_set_array[i];
ActionSet *action_set = create_action_set(xr_action_set->get_name(), xr_action_set->get_localized_name(), xr_action_set->get_priority());
if (!action_set) {
continue;
@@ -147,20 +147,20 @@ void OpenXRInterface::_load_action_map() {
Ref<OpenXRAction> xr_action = actions[j];
PackedStringArray toplevel_paths = xr_action->get_toplevel_paths();
- Vector<Tracker *> trackers;
+ Vector<Tracker *> trackers_new;
for (int k = 0; k < toplevel_paths.size(); k++) {
Tracker *tracker = find_tracker(toplevel_paths[k], true);
if (tracker) {
- trackers.push_back(tracker);
+ trackers_new.push_back(tracker);
}
}
Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers);
if (action) {
// we link our actions back to our trackers so we know which actions to check when we're processing our trackers
- for (int t = 0; t < trackers.size(); t++) {
- link_action_to_tracker(trackers[t], action);
+ for (int t = 0; t < trackers_new.size(); t++) {
+ link_action_to_tracker(trackers_new[t], action);
}
// add this to our map for creating our interaction profiles
@@ -170,9 +170,9 @@ void OpenXRInterface::_load_action_map() {
}
// now do our suggestions
- Array interaction_profiles = action_map->get_interaction_profiles();
- for (int i = 0; i < interaction_profiles.size(); i++) {
- Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profiles[i];
+ Array interaction_profile_array = action_map->get_interaction_profiles();
+ for (int i = 0; i < interaction_profile_array.size(); i++) {
+ Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profile_array[i];
// Note, we can only have one entry per interaction profile so if it already exists we clear it out
RID ip = openxr_api->interaction_profile_create(xr_interaction_profile->get_interaction_profile_path());
@@ -202,8 +202,8 @@ void OpenXRInterface::_load_action_map() {
openxr_api->interaction_profile_suggest_bindings(ip);
// And record it in our array so we can clean it up later on
- if (interaction_profiles.has(ip)) {
- interaction_profiles.push_back(ip);
+ if (interaction_profile_array.has(ip)) {
+ interaction_profile_array.push_back(ip);
}
}
}
@@ -648,6 +648,22 @@ Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_as
return cm;
}
+RID OpenXRInterface::get_color_texture() {
+ if (openxr_api) {
+ return openxr_api->get_color_texture();
+ } else {
+ return RID();
+ }
+}
+
+RID OpenXRInterface::get_depth_texture() {
+ if (openxr_api) {
+ return openxr_api->get_depth_texture();
+ } else {
+ return RID();
+ }
+}
+
void OpenXRInterface::process() {
if (openxr_api) {
// do our normal process
@@ -707,6 +723,7 @@ bool OpenXRInterface::pre_draw_viewport(RID p_render_target) {
Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) {
Vector<BlitToScreen> blit_to_screen;
+#ifndef ANDROID_ENABLED
// If separate HMD we should output one eye to screen
if (p_screen_rect != Rect2()) {
BlitToScreen blit;
@@ -732,6 +749,7 @@ Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, co
blit.dst_rect = dst_rect;
blit_to_screen.push_back(blit);
}
+#endif
if (openxr_api) {
openxr_api->post_draw_viewport(p_render_target);
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index 3765f18637..72935b039c 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -126,6 +126,9 @@ public:
virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override;
virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override;
+ virtual RID get_color_texture() override;
+ virtual RID get_depth_texture() override;
+
virtual void process() override;
virtual void pre_render() override;
bool pre_draw_viewport(RID p_render_target) override;
diff --git a/modules/openxr/util.h b/modules/openxr/util.h
index 5c79890830..1b5e0451dc 100644
--- a/modules/openxr/util.h
+++ b/modules/openxr/util.h
@@ -53,58 +53,58 @@
#define EXT_INIT_XR_FUNC(name) INIT_XR_FUNC(openxr_api, name)
#define OPENXR_API_INIT_XR_FUNC(name) INIT_XR_FUNC(this, name)
-#define EXT_PROTO_XRRESULT_FUNC1(func_name, arg1_type, arg1) \
- PFN_##func_name func_name##_ptr = nullptr; \
- XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type arg1) const { \
- if (!func_name##_ptr) { \
- return XR_ERROR_HANDLE_INVALID; \
- } \
- return (*func_name##_ptr)(arg1); \
+#define EXT_PROTO_XRRESULT_FUNC1(func_name, arg1_type, arg1) \
+ PFN_##func_name func_name##_ptr = nullptr; \
+ XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1) const { \
+ if (!func_name##_ptr) { \
+ return XR_ERROR_HANDLE_INVALID; \
+ } \
+ return (*func_name##_ptr)(p_##arg1); \
}
-#define EXT_PROTO_XRRESULT_FUNC2(func_name, arg1_type, arg1, arg2_type, arg2) \
- PFN_##func_name func_name##_ptr = nullptr; \
- XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type arg1, UNPACK arg2_type arg2) const { \
- if (!func_name##_ptr) { \
- return XR_ERROR_HANDLE_INVALID; \
- } \
- return (*func_name##_ptr)(arg1, arg2); \
+#define EXT_PROTO_XRRESULT_FUNC2(func_name, arg1_type, arg1, arg2_type, arg2) \
+ PFN_##func_name func_name##_ptr = nullptr; \
+ XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2) const { \
+ if (!func_name##_ptr) { \
+ return XR_ERROR_HANDLE_INVALID; \
+ } \
+ return (*func_name##_ptr)(p_##arg1, p_##arg2); \
}
-#define EXT_PROTO_XRRESULT_FUNC3(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3) \
- PFN_##func_name func_name##_ptr = nullptr; \
- XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type arg1, UNPACK arg2_type arg2, UNPACK arg3_type arg3) const { \
- if (!func_name##_ptr) { \
- return XR_ERROR_HANDLE_INVALID; \
- } \
- return (*func_name##_ptr)(arg1, arg2, arg3); \
+#define EXT_PROTO_XRRESULT_FUNC3(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3) \
+ PFN_##func_name func_name##_ptr = nullptr; \
+ XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2, UNPACK arg3_type p_##arg3) const { \
+ if (!func_name##_ptr) { \
+ return XR_ERROR_HANDLE_INVALID; \
+ } \
+ return (*func_name##_ptr)(p_##arg1, p_##arg2, p_##arg3); \
}
-#define EXT_PROTO_XRRESULT_FUNC4(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4) \
- PFN_##func_name func_name##_ptr = nullptr; \
- XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type arg1, UNPACK arg2_type arg2, UNPACK arg3_type arg3, UNPACK arg4_type arg4) const { \
- if (!func_name##_ptr) { \
- return XR_ERROR_HANDLE_INVALID; \
- } \
- return (*func_name##_ptr)(arg1, arg2, arg3, arg4); \
+#define EXT_PROTO_XRRESULT_FUNC4(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4) \
+ PFN_##func_name func_name##_ptr = nullptr; \
+ XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2, UNPACK arg3_type p_##arg3, UNPACK arg4_type p_##arg4) const { \
+ if (!func_name##_ptr) { \
+ return XR_ERROR_HANDLE_INVALID; \
+ } \
+ return (*func_name##_ptr)(p_##arg1, p_##arg2, p_##arg3, p_##arg4); \
}
-#define EXT_PROTO_XRRESULT_FUNC5(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4, arg5_type, arg5) \
- PFN_##func_name func_name##_ptr = nullptr; \
- XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type arg1, UNPACK arg2_type arg2, UNPACK arg3_type arg3, UNPACK arg4_type arg4, UNPACK arg5_type arg5) const { \
- if (!func_name##_ptr) { \
- return XR_ERROR_HANDLE_INVALID; \
- } \
- return (*func_name##_ptr)(arg1, arg2, arg3, arg4, arg5); \
+#define EXT_PROTO_XRRESULT_FUNC5(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4, arg5_type, arg5) \
+ PFN_##func_name func_name##_ptr = nullptr; \
+ XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2, UNPACK arg3_type p_##arg3, UNPACK arg4_type p_##arg4, UNPACK arg5_type p_##arg5) const { \
+ if (!func_name##_ptr) { \
+ return XR_ERROR_HANDLE_INVALID; \
+ } \
+ return (*func_name##_ptr)(p_##arg1, p_##arg2, p_##arg3, p_##arg4, p_##arg5); \
}
-#define EXT_PROTO_XRRESULT_FUNC6(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4, arg5_type, arg5, arg6_type, arg6) \
- PFN_##func_name func_name##_ptr = nullptr; \
- XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type arg1, UNPACK arg2_type arg2, UNPACK arg3_type arg3, UNPACK arg4_type arg4, UNPACK arg5_type arg5, UNPACK arg6_type arg6) const { \
- if (!func_name##_ptr) { \
- return XR_ERROR_HANDLE_INVALID; \
- } \
- return (*func_name##_ptr)(arg1, arg2, arg3, arg4, arg5, arg6); \
+#define EXT_PROTO_XRRESULT_FUNC6(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4, arg5_type, arg5, arg6_type, arg6) \
+ PFN_##func_name func_name##_ptr = nullptr; \
+ XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2, UNPACK arg3_type p_##arg3, UNPACK arg4_type p_##arg4, UNPACK arg5_type p_##arg5, UNPACK arg6_type p_##arg6) const { \
+ if (!func_name##_ptr) { \
+ return XR_ERROR_HANDLE_INVALID; \
+ } \
+ return (*func_name##_ptr)(p_##arg1, p_##arg2, p_##arg3, p_##arg4, p_##arg5, p_##arg6); \
}
#endif // UTIL_H
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index 7ca8859ee6..6220e35b54 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -68,7 +68,7 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
env.Append(CPPDEFINES=["MODULE_MSDFGEN_ENABLED"])
lib = env_msdfgen.Library(
- f'msdfgen_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}',
+ f'msdfgen_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
thirdparty_msdfgen_sources,
)
env.Append(LIBS=[lib])
@@ -199,7 +199,7 @@ if env["freetype_enabled"]:
env.Append(CPPDEFINES=["MODULE_FREETYPE_ENABLED"])
lib = env_freetype.Library(
- f'freetype_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}',
+ f'freetype_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
thirdparty_freetype_sources,
)
env.Append(LIBS=[lib])
@@ -321,7 +321,7 @@ if env["freetype_enabled"]:
env.Append(CPPPATH=["../../../thirdparty/harfbuzz/src"])
lib = env_harfbuzz.Library(
- f'harfbuzz_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}',
+ f'harfbuzz_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
thirdparty_harfbuzz_sources,
)
env.Prepend(LIBS=[lib])
@@ -381,7 +381,7 @@ if env["graphite_enabled"] and env["freetype_enabled"]:
)
lib = env_graphite.Library(
- f'graphite_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}',
+ f'graphite_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
thirdparty_graphite_sources,
)
env.Append(LIBS=[lib])
@@ -640,9 +640,7 @@ env.Append(CPPPATH=["../../../thirdparty/icu4c/common/", "../../../thirdparty/ic
if env["platform"] == "windows":
env.Append(LIBS=["advapi32"])
-lib = env_icu.Library(
- f'icu_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}', thirdparty_icu_sources
-)
+lib = env_icu.Library(f'icu_builtin{env["suffix"]}{env["LIBSUFFIX"]}', thirdparty_icu_sources)
env.Append(LIBS=[lib])
env.Append(CPPDEFINES=["GDEXTENSION"])
@@ -662,7 +660,7 @@ if env["platform"] == "macos":
)
else:
library = env.SharedLibrary(
- f'./bin/libtextserver_advanced.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["SHLIBSUFFIX"]}',
+ f'./bin/libtextserver_advanced{env["suffix"]}{env["SHLIBSUFFIX"]}',
source=sources,
)
diff --git a/modules/text_server_adv/gdextension_build/text_server_adv.gdextension b/modules/text_server_adv/gdextension_build/text_server_adv.gdextension
index 11ed271ae9..c12fcfdfdf 100644
--- a/modules/text_server_adv/gdextension_build/text_server_adv.gdextension
+++ b/modules/text_server_adv/gdextension_build/text_server_adv.gdextension
@@ -4,9 +4,21 @@ entry_symbol = "textserver_advanced_init"
[libraries]
-linux.64.debug = "bin/libtextserver_advanced.linux.debug.64.so"
-linux.64.release = "bin/libtextserver_advanced.linux.release.64.so"
-windows.64.debug = "bin/libtextserver_advanced.windows.debug.64.dll"
-windows.64.release = "bin/libtextserver_advanced.windows.release.64.dll"
-macos.debug = "bin/libtextserver_advanced.macos.debug.framework"
-macos.release = "bin/libtextserver_advanced.macos.release.framework"
+linux.x86_64.debug = "bin/libtextserver_advanced.linux.template_debug.x86_64.so"
+linux.x86_64.release = "bin/libtextserver_advanced.linux.template_release.x86_64.so"
+linux.x86_32.debug = "bin/libtextserver_advanced.linux.template_debug.x86_32.so"
+linux.x86_32.release = "bin/libtextserver_advanced.linux.template_release.x86_32.so"
+linux.arm64.debug = "bin/libtextserver_advanced.linux.template_debug.arm64.so"
+linux.arm64.release = "bin/libtextserver_advanced.linux.template_release.arm64.so"
+linux.rv64.debug = "bin/libtextserver_advanced.linux.template_debug.rv64.so"
+linux.rv64.release = "bin/libtextserver_advanced.linux.template_release.rv64.so"
+
+windows.x86_64.debug = "bin/libtextserver_advanced.windows.template_debug.x86_64.dll"
+windows.x86_64.release = "bin/libtextserver_advanced.windows.template_release.x86_64.dll"
+windows.x86_32.debug = "bin/libtextserver_advanced.windows.template_debug.x86_32.dll"
+windows.x86_32.release = "bin/libtextserver_advanced.windows.template_release.x86_32.dll"
+windows.arm64.debug = "bin/libtextserver_advanced.windows.template_debug.arm64.dll"
+windows.arm64.release = "bin/libtextserver_advanced.windows.template_release.arm64.dll"
+
+macos.debug = "bin/libtextserver_advanced.macos.template_debug.framework"
+macos.release = "bin/libtextserver_advanced.macos.template_release.framework"
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index d0301eeae3..c9b0fa7dd5 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -5401,7 +5401,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
int32_t script_run_end = MIN(sd->script_iter->script_ranges[j].end, bidi_run_end);
char scr_buffer[5] = { 0, 0, 0, 0, 0 };
hb_tag_to_string(hb_script_to_iso15924_tag(sd->script_iter->script_ranges[j].script), scr_buffer);
- String script = String(scr_buffer);
+ String script_code = String(scr_buffer);
int spn_from = (is_rtl) ? 0 : sd->spans.size() - 1;
int spn_to = (is_rtl) ? sd->spans.size() : -1;
@@ -5441,7 +5441,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
fonts.push_back(sd->spans[k].fonts[0]);
}
for (int l = 1; l < font_count; l++) {
- if (_font_is_script_supported(span.fonts[l], script)) {
+ if (_font_is_script_supported(span.fonts[l], script_code)) {
if (_font_is_language_supported(span.fonts[l], span.language)) {
fonts.push_back(sd->spans[k].fonts[l]);
} else {
diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct
index eae8e031bf..8ed8f61a43 100644
--- a/modules/text_server_fb/gdextension_build/SConstruct
+++ b/modules/text_server_fb/gdextension_build/SConstruct
@@ -63,7 +63,7 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
env.Append(CPPDEFINES=["MODULE_MSDFGEN_ENABLED"])
lib = env_msdfgen.Library(
- f'msdfgen_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}',
+ f'msdfgen_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
thirdparty_msdfgen_sources,
)
env.Append(LIBS=[lib])
@@ -194,7 +194,7 @@ if env["freetype_enabled"]:
env.Append(CPPDEFINES=["MODULE_FREETYPE_ENABLED"])
lib = env_freetype.Library(
- f'freetype_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}',
+ f'freetype_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
thirdparty_freetype_sources,
)
env.Append(LIBS=[lib])
@@ -217,7 +217,7 @@ if env["platform"] == "macos":
)
else:
library = env.SharedLibrary(
- f'./bin/libtextserver_fallback.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["SHLIBSUFFIX"]}',
+ f'./bin/libtextserver_fallback{env["suffix"]}{env["SHLIBSUFFIX"]}',
source=sources,
)
diff --git a/modules/text_server_fb/gdextension_build/text_server_fb.gdextension b/modules/text_server_fb/gdextension_build/text_server_fb.gdextension
index 9236555d63..58a92e403b 100644
--- a/modules/text_server_fb/gdextension_build/text_server_fb.gdextension
+++ b/modules/text_server_fb/gdextension_build/text_server_fb.gdextension
@@ -4,9 +4,21 @@ entry_symbol = "textserver_fallback_init"
[libraries]
-linux.64.debug = "bin/libtextserver_fallback.linux.debug.64.so"
-linux.64.release = "bin/libtextserver_fallback.linux.release.64.so"
-windows.64.debug = "bin/libtextserver_fallback.windows.debug.64.dll"
-windows.64.release = "bin/libtextserver_fallback.windows.release.64.dll"
-macos.debug = "bin/libtextserver_fallback.macos.debug.framework"
-macos.release = "bin/libtextserver_fallback.macos.release.framework"
+linux.x86_64.debug = "bin/libtextserver_fallback.linux.template_debug.x86_64.so"
+linux.x86_64.release = "bin/libtextserver_fallback.linux.template_release.x86_64.so"
+linux.x86_32.debug = "bin/libtextserver_fallback.linux.template_debug.x86_32.so"
+linux.x86_32.release = "bin/libtextserver_fallback.linux.template_release.x86_32.so"
+linux.arm64.debug = "bin/libtextserver_fallback.linux.template_debug.arm64.so"
+linux.arm64.release = "bin/libtextserver_fallback.linux.template_release.arm64.so"
+linux.rv64.debug = "bin/libtextserver_fallback.linux.template_debug.rv64.so"
+linux.rv64.release = "bin/libtextserver_fallback.linux.template_release.rv64.so"
+
+windows.x86_64.debug = "bin/libtextserver_fallback.windows.template_debug.x86_64.dll"
+windows.x86_64.release = "bin/libtextserver_fallback.windows.template_release.x86_64.dll"
+windows.x86_32.debug = "bin/libtextserver_fallback.windows.template_debug.x86_32.dll"
+windows.x86_32.release = "bin/libtextserver_fallback.windows.template_release.x86_32.dll"
+windows.arm64.debug = "bin/libtextserver_fallback.windows.template_debug.arm64.dll"
+windows.arm64.release = "bin/libtextserver_fallback.windows.template_release.arm64.dll"
+
+macos.debug = "bin/libtextserver_fallback.macos.template_debug.framework"
+macos.release = "bin/libtextserver_fallback.macos.template_release.framework"
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index 57f055ca42..1284412cd8 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -33,8 +33,17 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4127)
+#endif
+
#include "thirdparty/misc/yuv2rgb.h"
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
int VideoStreamPlaybackTheora::buffer_data() {
char *buffer = ogg_sync_buffer(&oy, 4096);
@@ -242,16 +251,9 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
/* we're expecting more header packets. */
while ((theora_p && theora_p < 3) || (vorbis_p && vorbis_p < 3)) {
-#ifdef _MSC_VER
- // Make exception for these assignments in conditional expression.
-#pragma warning(push)
-#pragma warning(disable : 4706)
-#endif
-
- int ret;
-
/* look for further theora headers */
- while (theora_p && (theora_p < 3) && (ret = ogg_stream_packetout(&to, &op))) {
+ int ret = ogg_stream_packetout(&to, &op);
+ while (theora_p && theora_p < 3 && ret) {
if (ret < 0) {
fprintf(stderr, "Error parsing Theora stream headers; corrupt stream?\n");
clear();
@@ -262,11 +264,13 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
clear();
return;
}
+ ret = ogg_stream_packetout(&to, &op);
theora_p++;
}
/* look for more vorbis header packets */
- while (vorbis_p && (vorbis_p < 3) && (ret = ogg_stream_packetout(&vo, &op))) {
+ ret = ogg_stream_packetout(&vo, &op);
+ while (vorbis_p && vorbis_p < 3 && ret) {
if (ret < 0) {
fprintf(stderr, "Error parsing Vorbis stream headers; corrupt stream?\n");
clear();
@@ -282,12 +286,9 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
if (vorbis_p == 3) {
break;
}
+ ret = ogg_stream_packetout(&vo, &op);
}
-#ifdef _MSC_VER
-#pragma warning(pop)
-#endif
-
/* The header pages/packets will arrive before anything else we
care about, or the stream is not obeying spec */
@@ -464,12 +465,6 @@ void VideoStreamPlaybackTheora::update(double p_delta) {
while (theora_p && !frame_done) {
/* theora is one in, one out... */
if (ogg_stream_packetout(&to, &op) > 0) {
- if (false && pp_inc) {
- pp_level += pp_inc;
- th_decode_ctl(td, TH_DECCTL_SET_PPLEVEL, &pp_level,
- sizeof(pp_level));
- pp_inc = 0;
- }
/*HACK: This should be set after a seek or a gap, but we might not have
a granulepos for the first packet (we only have them for the last
packet on a page), so we just set it as often as we get it.
diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h
index 3efb653651..9e096ec8b7 100644
--- a/modules/theora/video_stream_theora.h
+++ b/modules/theora/video_stream_theora.h
@@ -76,7 +76,7 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback {
th_info ti;
th_comment tc;
th_dec_ctx *td = nullptr;
- vorbis_info vi;
+ vorbis_info vi = {};
vorbis_dsp_state vd;
vorbis_block vb;
vorbis_comment vc;
diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml
deleted file mode 100644
index 1978d2e7c6..0000000000
--- a/modules/websocket/doc_classes/WebSocketClient.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="WebSocketClient" inherits="WebSocketMultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
- <brief_description>
- A WebSocket client implementation.
- </brief_description>
- <description>
- This class implements a WebSocket client compatible with any RFC 6455-compliant WebSocket server.
- This client can be optionally used as a multiplayer peer for the [MultiplayerAPI].
- After starting the client ([method connect_to_url]), you will need to [method MultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]).
- You will receive appropriate signals when connecting, disconnecting, or when new data is available.
- [b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android.
- </description>
- <tutorials>
- </tutorials>
- <methods>
- <method name="connect_to_url">
- <return type="int" enum="Error" />
- <param index="0" name="url" type="String" />
- <param index="1" name="protocols" type="PackedStringArray" default="PackedStringArray()" />
- <param index="2" name="gd_mp_api" type="bool" default="false" />
- <param index="3" name="custom_headers" type="PackedStringArray" default="PackedStringArray()" />
- <description>
- Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested.
- If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a multiplayer peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted.
- If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]).
- You can optionally pass a list of [code]custom_headers[/code] to be added to the handshake HTTP request.
- [b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate.
- [b]Note:[/b] Specifying [code]custom_headers[/code] is not supported in Web exports due to browsers' restrictions.
- </description>
- </method>
- <method name="disconnect_from_host">
- <return type="void" />
- <param index="0" name="code" type="int" default="1000" />
- <param index="1" name="reason" type="String" default="&quot;&quot;" />
- <description>
- Disconnects this client from the connected host. See [method WebSocketPeer.close] for more information.
- </description>
- </method>
- <method name="get_connected_host" qualifiers="const">
- <return type="String" />
- <description>
- Returns the IP address of the currently connected host.
- </description>
- </method>
- <method name="get_connected_port" qualifiers="const">
- <return type="int" />
- <description>
- Returns the IP port of the currently connected host.
- </description>
- </method>
- </methods>
- <members>
- <member name="trusted_tls_certificate" type="X509Certificate" setter="set_trusted_tls_certificate" getter="get_trusted_tls_certificate">
- If specified, this [X509Certificate] will be the only one accepted when connecting to an TLS host. Any other certificate provided by the server will be regarded as invalid.
- [b]Note:[/b] Specifying a custom [code]trusted_tls_certificate[/code] is not supported in Web exports due to browsers' restrictions.
- </member>
- <member name="verify_tls" type="bool" setter="set_verify_tls_enabled" getter="is_verify_tls_enabled">
- If [code]true[/code], TLS certificate verification is enabled.
- [b]Note:[/b] You must specify the certificates to be used in the Project Settings for it to work when exported.
- </member>
- </members>
- <signals>
- <signal name="connection_closed">
- <param index="0" name="was_clean_close" type="bool" />
- <description>
- Emitted when the connection to the server is closed. [code]was_clean_close[/code] will be [code]true[/code] if the connection was shutdown cleanly.
- </description>
- </signal>
- <signal name="connection_error">
- <description>
- Emitted when the connection to the server fails.
- </description>
- </signal>
- <signal name="connection_established">
- <param index="0" name="protocol" type="String" />
- <description>
- Emitted when a connection with the server is established, [code]protocol[/code] will contain the sub-protocol agreed with the server.
- </description>
- </signal>
- <signal name="data_received">
- <description>
- Emitted when a WebSocket message is received.
- [b]Note:[/b] This signal is [i]not[/i] emitted when used as high-level multiplayer peer.
- </description>
- </signal>
- <signal name="server_close_request">
- <param index="0" name="code" type="int" />
- <param index="1" name="reason" type="String" />
- <description>
- Emitted when the server requests a clean close. You should keep polling until you get a [signal connection_closed] signal to achieve the clean close. See [method WebSocketPeer.close] for more details.
- </description>
- </signal>
- </signals>
-</class>
diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
index 4cc4d515e7..c4481b046b 100644
--- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
@@ -10,6 +10,42 @@
<tutorials>
</tutorials>
<methods>
+ <method name="close">
+ <return type="void" />
+ <description>
+ Closes this [MultiplayerPeer], resetting the state to [constant MultiplayerPeer.CONNECTION_CONNECTED].
+ [b]Note:[/b] To make sure remote peers receive a clean close prefer disconnecting clients via [method disconnect_peer].
+ </description>
+ </method>
+ <method name="create_client">
+ <return type="int" enum="Error" />
+ <param index="0" name="url" type="String" />
+ <param index="1" name="verify_tls" type="bool" default="true" />
+ <param index="2" name="tls_certificate" type="X509Certificate" default="null" />
+ <description>
+ Starts a new multiplayer client connecting to the given [param url]. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param tls_certificate] will be used to verify the TLS host.
+ [b]Note[/b]: It is recommended to specify the scheme part of the URL, i.e. the [param url] should start with either [code]ws://[/code] or [code]wss://[/code].
+ </description>
+ </method>
+ <method name="create_server">
+ <return type="int" enum="Error" />
+ <param index="0" name="port" type="int" />
+ <param index="1" name="bind_address" type="String" default="&quot;*&quot;" />
+ <param index="2" name="tls_key" type="CryptoKey" default="null" />
+ <param index="3" name="tls_certificate" type="X509Certificate" default="null" />
+ <description>
+ Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide a [param tls_key] and [param tls_certificate] to use TLS.
+ </description>
+ </method>
+ <method name="disconnect_peer">
+ <return type="void" />
+ <param index="0" name="id" type="int" />
+ <param index="1" name="code" type="int" default="1000" />
+ <param index="2" name="reason" type="String" default="&quot;&quot;" />
+ <description>
+ Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more information.
+ </description>
+ </method>
<method name="get_peer" qualifiers="const">
<return type="WebSocketPeer" />
<param index="0" name="peer_id" type="int" />
@@ -17,27 +53,39 @@
Returns the [WebSocketPeer] associated to the given [code]peer_id[/code].
</description>
</method>
- <method name="set_buffers">
- <return type="int" enum="Error" />
- <param index="0" name="input_buffer_size_kb" type="int" />
- <param index="1" name="input_max_packets" type="int" />
- <param index="2" name="output_buffer_size_kb" type="int" />
- <param index="3" name="output_max_packets" type="int" />
+ <method name="get_peer_address" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="id" type="int" />
<description>
- Configures the buffer sizes for this WebSocket peer. Default values can be specified in the Project Settings under [code]network/limits[/code]. For server, values are meant per connected peer.
- The first two parameters define the size and queued packets limits of the input buffer, the last two of the output buffer.
- Buffer sizes are expressed in KiB, so [code]4 = 2^12 = 4096 bytes[/code]. All parameters will be rounded up to the nearest power of two.
- [b]Note:[/b] Web exports only use the input buffer since the output one is managed by browsers.
+ Returns the IP address of the given peer.
</description>
</method>
- </methods>
- <signals>
- <signal name="peer_packet">
- <param index="0" name="peer_source" type="int" />
+ <method name="get_peer_port" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="id" type="int" />
<description>
- Emitted when a packet is received from a peer.
- [b]Note:[/b] This signal is only emitted when the client or server is configured to use Godot multiplayer API.
+ Returns the remote port of the given peer.
</description>
- </signal>
- </signals>
+ </method>
+ </methods>
+ <members>
+ <member name="handshake_headers" type="PackedStringArray" setter="set_handshake_headers" getter="get_handshake_headers" default="PackedStringArray()">
+ The extra headers to use during handshake. See [member WebSocketPeer.handshake_headers] for more details.
+ </member>
+ <member name="handshake_timeout" type="float" setter="set_handshake_timeout" getter="get_handshake_timeout" default="3.0">
+ The maximum time each peer can stay in a connecting state before being dropped.
+ </member>
+ <member name="inbound_buffer_size" type="int" setter="set_inbound_buffer_size" getter="get_inbound_buffer_size" default="65535">
+ The inbound buffer size for connected peers. See [member WebSocketPeer.inbound_buffer_size] for more details.
+ </member>
+ <member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="2048">
+ The maximum number of queued packets for connected peers. See [member WebSocketPeer.max_queued_packets] for more details.
+ </member>
+ <member name="outbound_buffer_size" type="int" setter="set_outbound_buffer_size" getter="get_outbound_buffer_size" default="65535">
+ The outbound buffer size for connected peers. See [member WebSocketPeer.outbound_buffer_size] for more details.
+ </member>
+ <member name="supported_protocols" type="PackedStringArray" setter="set_supported_protocols" getter="get_supported_protocols" default="PackedStringArray()">
+ The supported WebSocket sub-protocols. See [member WebSocketPeer.supported_protocols] for more details.
+ </member>
+ </members>
</class>
diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml
index 627b9c607c..fe0aae412e 100644
--- a/modules/websocket/doc_classes/WebSocketPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketPeer.xml
@@ -1,25 +1,82 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="WebSocketPeer" inherits="PacketPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- A class representing a specific WebSocket connection.
+ A WebSocket connection.
</brief_description>
<description>
- This class represents a specific WebSocket connection, allowing you to do lower level operations with it.
- You can choose to write to the socket in binary or text mode, and you can recognize the mode used for writing by the other peer.
+ This class represents WebSocket connection, and can be used as a WebSocket client (RFC 6455-compliant) or as a remote peer of a WebSocket server.
+ You can send WebSocket binary frames using [method PacketPeer.put_packet], and WebSocket text frames using [method send] (prefer text frames when interacting with text-based API). You can check the frame type of the last packet via [method was_string_packet].
+ To start a WebSocket client, first call [method connect_to_url], then regularly call [method poll] (e.g. during [Node] process). You can query the socket state via [method get_ready_state], get the number of pending packets using [method PacketPeer.get_available_packet_count], and retrieve them via [method PacketPeer.get_packet].
+ [codeblocks]
+ [gdscript]
+ extends Node
+
+ var socket = WebSocketPeer.new()
+
+ func _ready():
+ socket.connect_to_url("wss://example.com")
+
+ func _process(delta):
+ socket.poll()
+ var state = socket.get_ready_state()
+ if state == WebSocketPeer.STATE_OPEN:
+ while socket.get_available_packet_count():
+ print("Packet: ", socket.get_packet())
+ elif state == WebSocketPeer.STATE_CLOSING:
+ # Keep polling to achieve proper close.
+ pass
+ elif state == WebSocketPeer.STATE_CLOSED:
+ var code = socket.get_close_code()
+ var reason = socket.get_close_reason()
+ print("WebSocket closed with code: %d, reason %s. Clean: %s" % [code, reason, code != -1])
+ set_process(false) # Stop processing.
+ [/gdscript]
+ [/codeblocks]
+ To use the peer as part of a WebSocket server refer to [method accept_stream] and the online tutorial.
</description>
<tutorials>
</tutorials>
<methods>
+ <method name="accept_stream">
+ <return type="int" enum="Error" />
+ <param index="0" name="stream" type="StreamPeer" />
+ <description>
+ Accepts a peer connection performing the HTTP handshake as a WebSocket server. The [param stream] must be a valid TCP stream retrieved via [method TCPServer.take_connection], or a TLS stream accepted via [method StreamPeerTLS.accept_stream].
+ [b]Note:[/b] Not supported in Web exports due to browsers' restrictions.
+ </description>
+ </method>
<method name="close">
<return type="void" />
<param index="0" name="code" type="int" default="1000" />
<param index="1" name="reason" type="String" default="&quot;&quot;" />
<description>
- Closes this WebSocket connection. [code]code[/code] is the status code for the closure (see RFC 6455 section 7.4 for a list of valid status codes). [code]reason[/code] is the human readable reason for closing the connection (can be any UTF-8 string that's smaller than 123 bytes).
- [b]Note:[/b] To achieve a clean close, you will need to keep polling until either [signal WebSocketClient.connection_closed] or [signal WebSocketServer.client_disconnected] is received.
+ Closes this WebSocket connection. [param code] is the status code for the closure (see RFC 6455 section 7.4 for a list of valid status codes). [param reason] is the human readable reason for closing the connection (can be any UTF-8 string that's smaller than 123 bytes). If [param code] is negative, the connection will be closed immediately without notifying the remote peer.
+ [b]Note:[/b] To achieve a clean close, you will need to keep polling until [constant STATE_CLOSED] is reached.
[b]Note:[/b] The Web export might not support all status codes. Please refer to browser-specific documentation for more details.
</description>
</method>
+ <method name="connect_to_url">
+ <return type="int" enum="Error" />
+ <param index="0" name="url" type="String" />
+ <param index="1" name="verify_tls" type="bool" default="true" />
+ <param index="2" name="trusted_tls_certificate" type="X509Certificate" default="null" />
+ <description>
+ Connects to the given URL. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param trusted_tls_certificate] will be the only one accepted when connecting to a TLS host.
+ [b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate.
+ </description>
+ </method>
+ <method name="get_close_code" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the received WebSocket close frame status code, or [code]-1[/code] when the connection was not cleanly closed. Only call this method when [method get_ready_state] returns [constant STATE_CLOSED].
+ </description>
+ </method>
+ <method name="get_close_reason" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns the received WebSocket close frame status reason string. Only call this method when [method get_ready_state] returns [constant STATE_CLOSED].
+ </description>
+ </method>
<method name="get_connected_host" qualifiers="const">
<return type="String" />
<description>
@@ -40,31 +97,51 @@
Returns the current amount of data in the outbound websocket buffer. [b]Note:[/b] Web exports use WebSocket.bufferedAmount, while other platforms use an internal buffer.
</description>
</method>
- <method name="get_write_mode" qualifiers="const">
- <return type="int" enum="WebSocketPeer.WriteMode" />
+ <method name="get_ready_state" qualifiers="const">
+ <return type="int" enum="WebSocketPeer.State" />
<description>
- Gets the current selected write mode. See [enum WriteMode].
+ Returns the ready state of the connection. See [enum State].
</description>
</method>
- <method name="is_connected_to_host" qualifiers="const">
- <return type="bool" />
+ <method name="get_requested_url" qualifiers="const">
+ <return type="String" />
<description>
- Returns [code]true[/code] if this peer is currently connected.
+ Returns the URL requested by this peer. The URL is derived from the [code]url[/code] passed to [method connect_to_url] or from the HTTP headers when acting as server (i.e. when using [method accept_stream]).
</description>
</method>
- <method name="set_no_delay">
+ <method name="get_selected_protocol" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns the selected WebSocket sub-protocol for this connection or an empty string if the sub-protocol has not been selected yet.
+ </description>
+ </method>
+ <method name="poll">
<return type="void" />
- <param index="0" name="enabled" type="bool" />
<description>
- Disable Nagle's algorithm on the underling TCP socket (default). See [method StreamPeerTCP.set_no_delay] for more information.
- [b]Note:[/b] Not available in the Web export.
+ Updates the connection state and receive incoming packets. Call this function regularly to keep it in a clean state.
</description>
</method>
- <method name="set_write_mode">
+ <method name="send">
+ <return type="int" enum="Error" />
+ <param index="0" name="message" type="PackedByteArray" />
+ <param index="1" name="write_mode" type="int" enum="WebSocketPeer.WriteMode" default="1" />
+ <description>
+ Sends the given [param message] using the desired [param write_mode]. When sending a [String], prefer using [method send_text].
+ </description>
+ </method>
+ <method name="send_text">
+ <return type="int" enum="Error" />
+ <param index="0" name="message" type="String" />
+ <description>
+ Sends the given [param message] using WebSocket text mode. Perfer this method over [method PacketPeer.put_packet] when interacting with third-party text-based API (e.g. when using [JSON] formatted messages).
+ </description>
+ </method>
+ <method name="set_no_delay">
<return type="void" />
- <param index="0" name="mode" type="int" enum="WebSocketPeer.WriteMode" />
+ <param index="0" name="enabled" type="bool" />
<description>
- Sets the socket to use the given [enum WriteMode].
+ Disable Nagle's algorithm on the underling TCP socket (default). See [method StreamPeerTCP.set_no_delay] for more information.
+ [b]Note:[/b] Not available in the Web export.
</description>
</method>
<method name="was_string_packet" qualifiers="const">
@@ -74,6 +151,24 @@
</description>
</method>
</methods>
+ <members>
+ <member name="handshake_headers" type="PackedStringArray" setter="set_handshake_headers" getter="get_handshake_headers" default="PackedStringArray()">
+ The extra HTTP headers to be sent during the WebSocket handshake.
+ [b]Note:[/b] Not supported in Web exports due to browsers' restrictions.
+ </member>
+ <member name="inbound_buffer_size" type="int" setter="set_inbound_buffer_size" getter="get_inbound_buffer_size" default="65535">
+ The size of the input buffer in bytes (roughly the maximum amount of memory that will be allocated for the inbound packets).
+ </member>
+ <member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="2048">
+ The maximum amount of packets that will be allowed in the queues (both inbound and outbound).
+ </member>
+ <member name="outbound_buffer_size" type="int" setter="set_outbound_buffer_size" getter="get_outbound_buffer_size" default="65535">
+ The size of the input buffer in bytes (roughly the maximum amount of memory that will be allocated for the outbound packets).
+ </member>
+ <member name="supported_protocols" type="PackedStringArray" setter="set_supported_protocols" getter="get_supported_protocols" default="PackedStringArray()">
+ The WebSocket sub-protocols allowed during the WebSocket handshake.
+ </member>
+ </members>
<constants>
<constant name="WRITE_MODE_TEXT" value="0" enum="WriteMode">
Specifies that WebSockets messages should be transferred as text payload (only valid UTF-8 is allowed).
@@ -81,5 +176,17 @@
<constant name="WRITE_MODE_BINARY" value="1" enum="WriteMode">
Specifies that WebSockets messages should be transferred as binary payload (any byte combination is allowed).
</constant>
+ <constant name="STATE_CONNECTING" value="0" enum="State">
+ Socket has been created. The connection is not yet open.
+ </constant>
+ <constant name="STATE_OPEN" value="1" enum="State">
+ The connection is open and ready to communicate.
+ </constant>
+ <constant name="STATE_CLOSING" value="2" enum="State">
+ The connection is in the process of closing. This means a close request has been sent to the remote peer but confirmation has not been received.
+ </constant>
+ <constant name="STATE_CLOSED" value="3" enum="State">
+ The connection is closed or couldn't be opened.
+ </constant>
</constants>
</class>
diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml
deleted file mode 100644
index 07a55b73f1..0000000000
--- a/modules/websocket/doc_classes/WebSocketServer.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="WebSocketServer" inherits="WebSocketMultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
- <brief_description>
- A WebSocket server implementation.
- </brief_description>
- <description>
- This class implements a WebSocket server that can also support the high-level multiplayer API.
- After starting the server ([method listen]), you will need to [method MultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). When clients connect, disconnect, or send data, you will receive the appropriate signal.
- [b]Note:[/b] Not available in Web exports.
- [b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android.
- </description>
- <tutorials>
- </tutorials>
- <methods>
- <method name="disconnect_peer">
- <return type="void" />
- <param index="0" name="id" type="int" />
- <param index="1" name="code" type="int" default="1000" />
- <param index="2" name="reason" type="String" default="&quot;&quot;" />
- <description>
- Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more information.
- </description>
- </method>
- <method name="get_peer_address" qualifiers="const">
- <return type="String" />
- <param index="0" name="id" type="int" />
- <description>
- Returns the IP address of the given peer.
- </description>
- </method>
- <method name="get_peer_port" qualifiers="const">
- <return type="int" />
- <param index="0" name="id" type="int" />
- <description>
- Returns the remote port of the given peer.
- </description>
- </method>
- <method name="has_peer" qualifiers="const">
- <return type="bool" />
- <param index="0" name="id" type="int" />
- <description>
- Returns [code]true[/code] if a peer with the given ID is connected.
- </description>
- </method>
- <method name="is_listening" qualifiers="const">
- <return type="bool" />
- <description>
- Returns [code]true[/code] if the server is actively listening on a port.
- </description>
- </method>
- <method name="listen">
- <return type="int" enum="Error" />
- <param index="0" name="port" type="int" />
- <param index="1" name="protocols" type="PackedStringArray" default="PackedStringArray()" />
- <param index="2" name="gd_mp_api" type="bool" default="false" />
- <description>
- Starts listening on the given port.
- You can specify the desired subprotocols via the "protocols" array. If the list empty (default), no sub-protocol will be requested.
- If [code]true[/code] is passed as [code]gd_mp_api[/code], the server will behave like a multiplayer peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted.
- If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.), on the [WebSocketPeer] returned via [code]get_peer(id)[/code] to communicate with the peer with given [code]id[/code] (e.g. [code]get_peer(id).get_available_packet_count[/code]).
- </description>
- </method>
- <method name="set_extra_headers">
- <return type="void" />
- <param index="0" name="headers" type="PackedStringArray" default="PackedStringArray()" />
- <description>
- Sets additional headers to be sent to clients during the HTTP handshake.
- </description>
- </method>
- <method name="stop">
- <return type="void" />
- <description>
- Stops the server and clear its state.
- </description>
- </method>
- </methods>
- <members>
- <member name="bind_ip" type="String" setter="set_bind_ip" getter="get_bind_ip" default="&quot;*&quot;">
- When not set to [code]*[/code] will restrict incoming connections to the specified IP address. Setting [code]bind_ip[/code] to [code]127.0.0.1[/code] will cause the server to listen only to the local host.
- </member>
- <member name="ca_chain" type="X509Certificate" setter="set_ca_chain" getter="get_ca_chain">
- When using TLS (see [member private_key] and [member tls_certificate]), you can set this to a valid [X509Certificate] to be provided as additional CA chain information during the TLS handshake.
- </member>
- <member name="handshake_timeout" type="float" setter="set_handshake_timeout" getter="get_handshake_timeout" default="3.0">
- The time in seconds before a pending client (i.e. a client that has not yet finished the HTTP handshake) is considered stale and forcefully disconnected.
- </member>
- <member name="private_key" type="CryptoKey" setter="set_private_key" getter="get_private_key">
- When set to a valid [CryptoKey] (along with [member tls_certificate]) will cause the server to require TLS instead of regular TCP (i.e. the [code]wss://[/code] protocol).
- </member>
- <member name="tls_certificate" type="X509Certificate" setter="set_tls_certificate" getter="get_tls_certificate">
- When set to a valid [X509Certificate] (along with [member private_key]) will cause the server to require TLS instead of regular TCP (i.e. the [code]wss://[/code] protocol).
- </member>
- </members>
- <signals>
- <signal name="client_close_request">
- <param index="0" name="id" type="int" />
- <param index="1" name="code" type="int" />
- <param index="2" name="reason" type="String" />
- <description>
- Emitted when a client requests a clean close. You should keep polling until you get a [signal client_disconnected] signal with the same [code]id[/code] to achieve the clean close. See [method WebSocketPeer.close] for more details.
- </description>
- </signal>
- <signal name="client_connected">
- <param index="0" name="id" type="int" />
- <param index="1" name="protocol" type="String" />
- <param index="2" name="resource_name" type="String" />
- <description>
- Emitted when a new client connects. "protocol" will be the sub-protocol agreed with the client, and "resource_name" will be the resource name of the URI the peer used.
- "resource_name" is a path (at the very least a single forward slash) and potentially a query string.
- </description>
- </signal>
- <signal name="client_disconnected">
- <param index="0" name="id" type="int" />
- <param index="1" name="was_clean_close" type="bool" />
- <description>
- Emitted when a client disconnects. [code]was_clean_close[/code] will be [code]true[/code] if the connection was shutdown cleanly.
- </description>
- </signal>
- <signal name="data_received">
- <param index="0" name="id" type="int" />
- <description>
- Emitted when a new message is received.
- [b]Note:[/b] This signal is [i]not[/i] emitted when used as high-level multiplayer peer.
- </description>
- </signal>
- </signals>
-</class>
diff --git a/modules/websocket/editor/editor_debugger_server_websocket.cpp b/modules/websocket/editor/editor_debugger_server_websocket.cpp
index 0443147d98..1c4ebd0f55 100644
--- a/modules/websocket/editor/editor_debugger_server_websocket.cpp
+++ b/modules/websocket/editor/editor_debugger_server_websocket.cpp
@@ -38,18 +38,25 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
-void EditorDebuggerServerWebSocket::_peer_connected(int p_id, String _protocol) {
- pending_peers.push_back(p_id);
-}
-
-void EditorDebuggerServerWebSocket::_peer_disconnected(int p_id, bool p_was_clean) {
- if (pending_peers.find(p_id)) {
- pending_peers.erase(p_id);
- }
-}
-
void EditorDebuggerServerWebSocket::poll() {
- server->poll();
+ if (pending_peer.is_null() && tcp_server->is_connection_available()) {
+ Ref<WebSocketPeer> peer;
+ Error err = peer->accept_stream(tcp_server->take_connection());
+ if (err == OK) {
+ pending_timer = OS::get_singleton()->get_ticks_msec();
+ pending_peer = peer;
+ }
+ }
+ if (pending_peer.is_valid() && pending_peer->get_ready_state() != WebSocketPeer::STATE_OPEN) {
+ pending_peer->poll();
+ WebSocketPeer::State ready_state = pending_peer->get_ready_state();
+ if (ready_state != WebSocketPeer::STATE_CONNECTING && ready_state != WebSocketPeer::STATE_OPEN) {
+ pending_peer.unref(); // Failed.
+ }
+ if (ready_state == WebSocketPeer::STATE_CONNECTING && OS::get_singleton()->get_ticks_msec() - pending_timer > 3000) {
+ pending_peer.unref(); // Timeout.
+ }
+ }
}
String EditorDebuggerServerWebSocket::get_uri() const {
@@ -69,15 +76,10 @@ Error EditorDebuggerServerWebSocket::start(const String &p_uri) {
ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER);
}
- // Set up the server
- server->set_bind_ip(bind_host);
- Vector<String> compatible_protocols;
- compatible_protocols.push_back("binary"); // compatibility with EMSCRIPTEN TCP-to-WebSocket layer.
-
// Try listening on ports
const int max_attempts = 5;
for (int attempt = 1;; ++attempt) {
- const Error err = server->listen(bind_port, compatible_protocols);
+ const Error err = tcp_server->listen(bind_port, bind_host);
if (err == OK) {
break;
}
@@ -96,31 +98,27 @@ Error EditorDebuggerServerWebSocket::start(const String &p_uri) {
}
void EditorDebuggerServerWebSocket::stop() {
- server->stop();
- pending_peers.clear();
+ pending_peer.unref();
+ tcp_server->stop();
}
bool EditorDebuggerServerWebSocket::is_active() const {
- return server->is_listening();
+ return tcp_server->is_listening();
}
bool EditorDebuggerServerWebSocket::is_connection_available() const {
- return pending_peers.size() > 0;
+ return pending_peer.is_valid() && pending_peer->get_ready_state() == WebSocketPeer::STATE_OPEN;
}
Ref<RemoteDebuggerPeer> EditorDebuggerServerWebSocket::take_connection() {
ERR_FAIL_COND_V(!is_connection_available(), Ref<RemoteDebuggerPeer>());
- RemoteDebuggerPeer *peer = memnew(RemoteDebuggerPeerWebSocket(server->get_peer(pending_peers[0])));
- pending_peers.pop_front();
+ RemoteDebuggerPeer *peer = memnew(RemoteDebuggerPeerWebSocket(pending_peer));
+ pending_peer.unref();
return peer;
}
EditorDebuggerServerWebSocket::EditorDebuggerServerWebSocket() {
- server = Ref<WebSocketServer>(WebSocketServer::create());
- int max_pkts = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages");
- server->set_buffers(8192, max_pkts, 8192, max_pkts);
- server->connect("client_connected", callable_mp(this, &EditorDebuggerServerWebSocket::_peer_connected));
- server->connect("client_disconnected", callable_mp(this, &EditorDebuggerServerWebSocket::_peer_disconnected));
+ tcp_server.instantiate();
}
EditorDebuggerServerWebSocket::~EditorDebuggerServerWebSocket() {
diff --git a/modules/websocket/editor/editor_debugger_server_websocket.h b/modules/websocket/editor/editor_debugger_server_websocket.h
index 7c0705302d..31e54cb5df 100644
--- a/modules/websocket/editor/editor_debugger_server_websocket.h
+++ b/modules/websocket/editor/editor_debugger_server_websocket.h
@@ -33,15 +33,18 @@
#ifdef TOOLS_ENABLED
-#include "../websocket_server.h"
+#include "../websocket_peer.h"
+
+#include "core/io/tcp_server.h"
#include "editor/debugger/editor_debugger_server.h"
class EditorDebuggerServerWebSocket : public EditorDebuggerServer {
GDCLASS(EditorDebuggerServerWebSocket, EditorDebuggerServer);
private:
- Ref<WebSocketServer> server;
- List<int> pending_peers;
+ Ref<TCPServer> tcp_server;
+ Ref<WebSocketPeer> pending_peer;
+ uint64_t pending_timer = 0;
String endpoint;
public:
diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp
deleted file mode 100644
index 933a1f43e9..0000000000
--- a/modules/websocket/emws_client.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-/*************************************************************************/
-/* emws_client.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 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. */
-/*************************************************************************/
-
-#ifdef WEB_ENABLED
-
-#include "emws_client.h"
-
-#include "core/config/project_settings.h"
-#include "core/io/ip.h"
-#include "emscripten.h"
-
-void EMWSClient::_esws_on_connect(void *obj, char *proto) {
- EMWSClient *client = static_cast<EMWSClient *>(obj);
- client->_is_connecting = false;
- client->_on_connect(String(proto));
-}
-
-void EMWSClient::_esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string) {
- EMWSClient *client = static_cast<EMWSClient *>(obj);
-
- Error err = static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1);
- if (err == OK) {
- client->_on_peer_packet();
- }
-}
-
-void EMWSClient::_esws_on_error(void *obj) {
- EMWSClient *client = static_cast<EMWSClient *>(obj);
- client->_is_connecting = false;
- client->_on_error();
-}
-
-void EMWSClient::_esws_on_close(void *obj, int code, const char *reason, int was_clean) {
- EMWSClient *client = static_cast<EMWSClient *>(obj);
- client->_on_close_request(code, String(reason));
- client->_is_connecting = false;
- client->disconnect_from_host();
- client->_on_disconnect(was_clean != 0);
-}
-
-Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector<String> p_protocols, const Vector<String> p_custom_headers) {
- if (_js_id) {
- godot_js_websocket_destroy(_js_id);
- _js_id = 0;
- }
-
- String proto_string;
- for (int i = 0; i < p_protocols.size(); i++) {
- if (i != 0) {
- proto_string += ",";
- }
- proto_string += p_protocols[i];
- }
-
- String str = "ws://";
-
- if (p_custom_headers.size()) {
- WARN_PRINT_ONCE("Custom headers are not supported in Web platform.");
- }
- if (p_tls) {
- str = "wss://";
- if (tls_cert.is_valid()) {
- WARN_PRINT_ONCE("Custom SSL certificate is not supported in Web platform.");
- }
- }
- str += p_host + ":" + itos(p_port) + p_path;
- _is_connecting = true;
-
- _js_id = godot_js_websocket_create(this, str.utf8().get_data(), proto_string.utf8().get_data(), &_esws_on_connect, &_esws_on_message, &_esws_on_error, &_esws_on_close);
- if (!_js_id) {
- return FAILED;
- }
-
- static_cast<Ref<EMWSPeer>>(_peer)->set_sock(_js_id, _in_buf_size, _in_pkt_size, _out_buf_size);
-
- return OK;
-}
-
-void EMWSClient::poll() {
-}
-
-Ref<WebSocketPeer> EMWSClient::get_peer(int p_peer_id) const {
- return _peer;
-}
-
-MultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() const {
- if (_peer->is_connected_to_host()) {
- if (_is_connecting) {
- return CONNECTION_CONNECTING;
- }
- return CONNECTION_CONNECTED;
- }
-
- return CONNECTION_DISCONNECTED;
-}
-
-void EMWSClient::disconnect_from_host(int p_code, String p_reason) {
- _peer->close(p_code, p_reason);
-}
-
-IPAddress EMWSClient::get_connected_host() const {
- ERR_FAIL_V_MSG(IPAddress(), "Not supported in Web export.");
-}
-
-uint16_t EMWSClient::get_connected_port() const {
- ERR_FAIL_V_MSG(0, "Not supported in Web export.");
-}
-
-int EMWSClient::get_max_packet_size() const {
- return (1 << _in_buf_size) - PROTO_SIZE;
-}
-
-Error EMWSClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) {
- _in_buf_size = nearest_shift(p_in_buffer - 1) + 10;
- _in_pkt_size = nearest_shift(p_in_packets - 1);
- _out_buf_size = nearest_shift(p_out_buffer - 1) + 10;
- return OK;
-}
-
-EMWSClient::EMWSClient() {
- _peer = Ref<EMWSPeer>(memnew(EMWSPeer));
-}
-
-EMWSClient::~EMWSClient() {
- disconnect_from_host();
- _peer = Ref<EMWSPeer>();
- if (_js_id) {
- godot_js_websocket_destroy(_js_id);
- }
-}
-
-#endif // WEB_ENABLED
diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp
index 859c92b457..5f3cb76852 100644
--- a/modules/websocket/emws_peer.cpp
+++ b/modules/websocket/emws_peer.cpp
@@ -34,55 +34,116 @@
#include "core/io/ip.h"
-void EMWSPeer::set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size) {
- peer_sock = p_sock;
- _in_buffer.resize(p_in_pkt_size, p_in_buf_size);
- _packet_buffer.resize((1 << p_in_buf_size));
- _out_buf_size = p_out_buf_size;
+void EMWSPeer::_esws_on_connect(void *p_obj, char *p_proto) {
+ EMWSPeer *peer = static_cast<EMWSPeer *>(p_obj);
+ peer->ready_state = STATE_OPEN;
+ peer->selected_protocol.parse_utf8(p_proto);
}
-void EMWSPeer::set_write_mode(WriteMode p_mode) {
- write_mode = p_mode;
+void EMWSPeer::_esws_on_message(void *p_obj, const uint8_t *p_data, int p_data_size, int p_is_string) {
+ EMWSPeer *peer = static_cast<EMWSPeer *>(p_obj);
+ uint8_t is_string = p_is_string ? 1 : 0;
+ peer->in_buffer.write_packet(p_data, p_data_size, &is_string);
}
-EMWSPeer::WriteMode EMWSPeer::get_write_mode() const {
- return write_mode;
+void EMWSPeer::_esws_on_error(void *p_obj) {
+ EMWSPeer *peer = static_cast<EMWSPeer *>(p_obj);
+ peer->ready_state = STATE_CLOSED;
}
-Error EMWSPeer::read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string) {
- uint8_t is_string = p_is_string ? 1 : 0;
- return _in_buffer.write_packet(p_data, p_size, &is_string);
+void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int p_was_clean) {
+ EMWSPeer *peer = static_cast<EMWSPeer *>(p_obj);
+ peer->close_code = p_code;
+ peer->close_reason.parse_utf8(p_reason);
+ peer->ready_state = STATE_CLOSED;
}
-Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
- ERR_FAIL_COND_V(_out_buf_size && ((uint64_t)godot_js_websocket_buffered_amount(peer_sock) + p_buffer_size >= (1ULL << _out_buf_size)), ERR_OUT_OF_MEMORY);
+Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate) {
+ ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE);
+ _clear();
+
+ String host;
+ String path;
+ String scheme;
+ int port = 0;
+ Error err = p_url.parse_url(scheme, host, port, path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);
+
+ if (scheme.is_empty()) {
+ scheme = "ws://";
+ }
+ ERR_FAIL_COND_V_MSG(scheme != "ws://" && scheme != "wss://", ERR_INVALID_PARAMETER, vformat("Invalid protocol: \"%s\" (must be either \"ws://\" or \"wss://\").", scheme));
+
+ String proto_string;
+ for (int i = 0; i < supported_protocols.size(); i++) {
+ if (i != 0) {
+ proto_string += ",";
+ }
+ proto_string += supported_protocols[i];
+ }
+
+ if (handshake_headers.size()) {
+ WARN_PRINT_ONCE("Custom headers are not supported in Web platform.");
+ }
+ if (p_tls_certificate.is_valid()) {
+ WARN_PRINT_ONCE("Custom SSL certificates are not supported in Web platform.");
+ }
- int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0;
+ requested_url = scheme + host;
- if (godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, is_bin) != 0) {
+ if (port && ((scheme == "ws://" && port != 80) || (scheme == "wss://" && port != 443))) {
+ requested_url += ":" + String::num(port);
+ }
+
+ peer_sock = godot_js_websocket_create(this, requested_url.utf8().get_data(), proto_string.utf8().get_data(), &_esws_on_connect, &_esws_on_message, &_esws_on_error, &_esws_on_close);
+ if (peer_sock == -1) {
return FAILED;
}
+ in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets);
+ packet_buffer.resize(inbound_buffer_size);
+ ready_state = STATE_CONNECTING;
+ return OK;
+}
+
+Error EMWSPeer::accept_stream(Ref<StreamPeer> p_stream) {
+ WARN_PRINT_ONCE("Acting as WebSocket server is not supported in Web platforms.");
+ return ERR_UNAVAILABLE;
+}
+
+Error EMWSPeer::_send(const uint8_t *p_buffer, int p_buffer_size, bool p_binary) {
+ ERR_FAIL_COND_V(outbound_buffer_size > 0 && (get_current_outbound_buffered_amount() + p_buffer_size >= outbound_buffer_size), ERR_OUT_OF_MEMORY);
+ if (godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, p_binary ? 1 : 0) != 0) {
+ return FAILED;
+ }
return OK;
}
+Error EMWSPeer::send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) {
+ return _send(p_buffer, p_buffer_size, p_mode == WRITE_MODE_TEXT);
+}
+
+Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
+ return _send(p_buffer, p_buffer_size, true);
+}
+
Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
- if (_in_buffer.packets_left() == 0) {
+ if (in_buffer.packets_left() == 0) {
return ERR_UNAVAILABLE;
}
int read = 0;
- Error err = _in_buffer.read_packet(_packet_buffer.ptrw(), _packet_buffer.size(), &_is_string, read);
+ Error err = in_buffer.read_packet(packet_buffer.ptrw(), packet_buffer.size(), &was_string, read);
ERR_FAIL_COND_V(err != OK, err);
- *r_buffer = _packet_buffer.ptr();
+ *r_buffer = packet_buffer.ptr();
r_buffer_size = read;
return OK;
}
int EMWSPeer::get_available_packet_count() const {
- return _in_buffer.packets_left();
+ return in_buffer.packets_left();
}
int EMWSPeer::get_current_outbound_buffered_amount() const {
@@ -93,20 +154,66 @@ int EMWSPeer::get_current_outbound_buffered_amount() const {
}
bool EMWSPeer::was_string_packet() const {
- return _is_string;
+ return was_string;
}
-bool EMWSPeer::is_connected_to_host() const {
- return peer_sock != -1;
+void EMWSPeer::_clear() {
+ if (peer_sock != -1) {
+ godot_js_websocket_destroy(peer_sock);
+ peer_sock = -1;
+ }
+ ready_state = STATE_CLOSED;
+ was_string = 0;
+ close_code = -1;
+ close_reason.clear();
+ selected_protocol.clear();
+ requested_url.clear();
+ in_buffer.clear();
+ packet_buffer.clear();
}
void EMWSPeer::close(int p_code, String p_reason) {
- if (peer_sock != -1) {
- godot_js_websocket_close(peer_sock, p_code, p_reason.utf8().get_data());
+ if (p_code < 0) {
+ if (peer_sock != -1) {
+ godot_js_websocket_destroy(peer_sock);
+ peer_sock = -1;
+ }
+ ready_state = STATE_CLOSED;
+ }
+ if (ready_state == STATE_CONNECTING || ready_state == STATE_OPEN) {
+ ready_state = STATE_CLOSING;
+ if (peer_sock != -1) {
+ godot_js_websocket_close(peer_sock, p_code, p_reason.utf8().get_data());
+ } else {
+ ready_state = STATE_CLOSED;
+ }
}
- _is_string = 0;
- _in_buffer.clear();
- peer_sock = -1;
+ in_buffer.clear();
+ packet_buffer.clear();
+}
+
+void EMWSPeer::poll() {
+ // Automatically polled by the navigator.
+}
+
+WebSocketPeer::State EMWSPeer::get_ready_state() const {
+ return ready_state;
+}
+
+int EMWSPeer::get_close_code() const {
+ return close_code;
+}
+
+String EMWSPeer::get_close_reason() const {
+ return close_reason;
+}
+
+String EMWSPeer::get_selected_protocol() const {
+ return selected_protocol;
+}
+
+String EMWSPeer::get_requested_url() const {
+ return requested_url;
}
IPAddress EMWSPeer::get_connected_host() const {
@@ -122,11 +229,10 @@ void EMWSPeer::set_no_delay(bool p_enabled) {
}
EMWSPeer::EMWSPeer() {
- close();
}
EMWSPeer::~EMWSPeer() {
- close();
+ _clear();
}
#endif // WEB_ENABLED
diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h
index cdbc9212a5..322cc3b700 100644
--- a/modules/websocket/emws_peer.h
+++ b/modules/websocket/emws_peer.h
@@ -54,33 +54,53 @@ extern void godot_js_websocket_destroy(int p_id);
}
class EMWSPeer : public WebSocketPeer {
- GDCIIMPL(EMWSPeer, WebSocketPeer);
-
private:
int peer_sock = -1;
- WriteMode write_mode = WRITE_MODE_BINARY;
- Vector<uint8_t> _packet_buffer;
- PacketBuffer<uint8_t> _in_buffer;
- uint8_t _is_string = 0;
- int _out_buf_size = 0;
+ State ready_state = STATE_CLOSED;
+ Vector<uint8_t> packet_buffer;
+ PacketBuffer<uint8_t> in_buffer;
+ uint8_t was_string = 0;
+ int close_code = -1;
+ String close_reason;
+ String selected_protocol;
+ String requested_url;
+
+ static WebSocketPeer *_create() { return memnew(EMWSPeer); }
+ static void _esws_on_connect(void *obj, char *proto);
+ static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string);
+ static void _esws_on_error(void *obj);
+ static void _esws_on_close(void *obj, int code, const char *reason, int was_clean);
+
+ void _clear();
+ Error _send(const uint8_t *p_buffer, int p_buffer_size, bool p_binary);
public:
- Error read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string);
- void set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size);
+ static void initialize() { WebSocketPeer::_create = EMWSPeer::_create; }
+
+ // PacketPeer
virtual int get_available_packet_count() const override;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
- virtual int get_max_packet_size() const override { return _packet_buffer.size(); };
- virtual int get_current_outbound_buffered_amount() const override;
+ virtual int get_max_packet_size() const override { return packet_buffer.size(); };
+ // WebSocketPeer
+ virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override;
+ virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) override;
+ virtual Error accept_stream(Ref<StreamPeer> p_stream) override;
virtual void close(int p_code = 1000, String p_reason = "") override;
- virtual bool is_connected_to_host() const override;
+ virtual void poll() override;
+
+ virtual State get_ready_state() const override;
+ virtual int get_close_code() const override;
+ virtual String get_close_reason() const override;
+ virtual int get_current_outbound_buffered_amount() const override;
+
virtual IPAddress get_connected_host() const override;
virtual uint16_t get_connected_port() const override;
+ virtual String get_selected_protocol() const override;
+ virtual String get_requested_url() const override;
- virtual WriteMode get_write_mode() const override;
- virtual void set_write_mode(WriteMode p_mode) override;
virtual bool was_string_packet() const override;
virtual void set_no_delay(bool p_enabled) override;
diff --git a/modules/websocket/packet_buffer.h b/modules/websocket/packet_buffer.h
index 7b4a164576..cd81dc43cd 100644
--- a/modules/websocket/packet_buffer.h
+++ b/modules/websocket/packet_buffer.h
@@ -41,32 +41,29 @@ private:
T info;
} _Packet;
- RingBuffer<_Packet> _packets;
+ Vector<_Packet> _packets;
+ int _queued = 0;
+ int _write_pos = 0;
+ int _read_pos = 0;
RingBuffer<uint8_t> _payload;
public:
Error write_packet(const uint8_t *p_payload, uint32_t p_size, const T *p_info) {
-#ifdef TOOLS_ENABLED
- // Verbose buffer warnings
- if (p_payload && _payload.space_left() < (int32_t)p_size) {
- ERR_PRINT("Buffer payload full! Dropping data.");
- ERR_FAIL_V(ERR_OUT_OF_MEMORY);
- }
- if (p_info && _packets.space_left() < 1) {
- ERR_PRINT("Too many packets in queue! Dropping data.");
- ERR_FAIL_V(ERR_OUT_OF_MEMORY);
- }
-#else
- ERR_FAIL_COND_V(p_payload && (uint32_t)_payload.space_left() < p_size, ERR_OUT_OF_MEMORY);
- ERR_FAIL_COND_V(p_info && _packets.space_left() < 1, ERR_OUT_OF_MEMORY);
-#endif
+ ERR_FAIL_COND_V_MSG(p_payload && (uint32_t)_payload.space_left() < p_size, ERR_OUT_OF_MEMORY, "Buffer payload full! Dropping data.");
+ ERR_FAIL_COND_V_MSG(p_info && _queued >= _packets.size(), ERR_OUT_OF_MEMORY, "Too many packets in queue! Dropping data.");
// If p_info is nullptr, only the payload is written
if (p_info) {
+ ERR_FAIL_COND_V(_write_pos > _packets.size(), ERR_OUT_OF_MEMORY);
_Packet p;
p.size = p_size;
- memcpy(&p.info, p_info, sizeof(T));
- _packets.write(p);
+ p.info = *p_info;
+ _packets.write[_write_pos] = p;
+ _queued += 1;
+ _write_pos++;
+ if (_write_pos >= _packets.size()) {
+ _write_pos = 0;
+ }
}
// If p_payload is nullptr, only the packet information is written.
@@ -78,9 +75,14 @@ public:
}
Error read_packet(uint8_t *r_payload, int p_bytes, T *r_info, int &r_read) {
- ERR_FAIL_COND_V(_packets.data_left() < 1, ERR_UNAVAILABLE);
- _Packet p;
- _packets.read(&p, 1);
+ ERR_FAIL_COND_V(_queued < 1, ERR_UNAVAILABLE);
+ _Packet p = _packets[_read_pos];
+ _read_pos += 1;
+ if (_read_pos >= _packets.size()) {
+ _read_pos = 0;
+ }
+ _queued -= 1;
+
ERR_FAIL_COND_V(_payload.data_left() < (int)p.size, ERR_BUG);
ERR_FAIL_COND_V(p_bytes < (int)p.size, ERR_OUT_OF_MEMORY);
@@ -90,22 +92,24 @@ public:
return OK;
}
- void discard_payload(int p_size) {
- _packets.decrease_write(p_size);
- }
-
- void resize(int p_pkt_shift, int p_buf_shift) {
- _packets.resize(p_pkt_shift);
+ void resize(int p_buf_shift, int p_max_packets) {
_payload.resize(p_buf_shift);
+ _packets.resize(p_max_packets);
+ _read_pos = 0;
+ _write_pos = 0;
+ _queued = 0;
}
int packets_left() const {
- return _packets.data_left();
+ return _queued;
}
void clear() {
_payload.resize(0);
_packets.resize(0);
+ _read_pos = 0;
+ _write_pos = 0;
+ _queued = 0;
}
PacketBuffer() {
diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp
index faa7021b2f..691b031bbd 100644
--- a/modules/websocket/register_types.cpp
+++ b/modules/websocket/register_types.cpp
@@ -33,16 +33,13 @@
#include "core/config/project_settings.h"
#include "core/error/error_macros.h"
-#include "websocket_client.h"
-#include "websocket_server.h"
+#include "websocket_multiplayer_peer.h"
+#include "websocket_peer.h"
#ifdef WEB_ENABLED
-#include "emscripten.h"
-#include "emws_client.h"
#include "emws_peer.h"
#else
-#include "wsl_client.h"
-#include "wsl_server.h"
+#include "wsl_peer.h"
#endif
#ifdef TOOLS_ENABLED
@@ -60,17 +57,12 @@ static void _editor_init_callback() {
void initialize_websocket_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
#ifdef WEB_ENABLED
- EMWSPeer::make_default();
- EMWSClient::make_default();
+ EMWSPeer::initialize();
#else
- WSLPeer::make_default();
- WSLClient::make_default();
- WSLServer::make_default();
+ WSLPeer::initialize();
#endif
- GDREGISTER_ABSTRACT_CLASS(WebSocketMultiplayerPeer);
- ClassDB::register_custom_instance_class<WebSocketServer>();
- ClassDB::register_custom_instance_class<WebSocketClient>();
+ GDREGISTER_CLASS(WebSocketMultiplayerPeer);
ClassDB::register_custom_instance_class<WebSocketPeer>();
}
@@ -85,4 +77,7 @@ void uninitialize_websocket_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
+#ifndef WEB_ENABLED
+ WSLPeer::deinitialize();
+#endif
}
diff --git a/modules/websocket/remote_debugger_peer_websocket.cpp b/modules/websocket/remote_debugger_peer_websocket.cpp
index f703873cbf..fc4f51b59b 100644
--- a/modules/websocket/remote_debugger_peer_websocket.cpp
+++ b/modules/websocket/remote_debugger_peer_websocket.cpp
@@ -36,27 +36,35 @@ Error RemoteDebuggerPeerWebSocket::connect_to_host(const String &p_uri) {
Vector<String> protocols;
protocols.push_back("binary"); // Compatibility for emscripten TCP-to-WebSocket.
- ws_client->connect_to_url(p_uri, protocols);
- ws_client->poll();
+ ws_peer = Ref<WebSocketPeer>(WebSocketPeer::create());
+ ws_peer->set_supported_protocols(protocols);
- if (ws_client->get_connection_status() == WebSocketClient::CONNECTION_DISCONNECTED) {
- ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(ws_client->get_connection_status()) + ".");
+ ws_peer->set_max_queued_packets(max_queued_messages);
+ ws_peer->set_inbound_buffer_size((1 << 23) - 1);
+ ws_peer->set_outbound_buffer_size((1 << 23) - 1);
+
+ Error err = ws_peer->connect_to_url(p_uri);
+ ERR_FAIL_COND_V(err != OK, err);
+
+ ws_peer->poll();
+ WebSocketPeer::State ready_state = ws_peer->get_ready_state();
+ if (ready_state != WebSocketPeer::STATE_CONNECTING && ready_state != WebSocketPeer::STATE_OPEN) {
+ ERR_PRINT(vformat("Remote Debugger: Unable to connect. State: %s.", ws_peer->get_ready_state()));
return FAILED;
}
- ws_peer = ws_client->get_peer(1);
-
return OK;
}
bool RemoteDebuggerPeerWebSocket::is_peer_connected() {
- return ws_peer.is_valid() && ws_peer->is_connected_to_host();
+ return ws_peer.is_valid() && (ws_peer->get_ready_state() == WebSocketPeer::STATE_OPEN || ws_peer->get_ready_state() == WebSocketPeer::STATE_CONNECTING);
}
void RemoteDebuggerPeerWebSocket::poll() {
- ws_client->poll();
+ ERR_FAIL_COND(ws_peer.is_null());
+ ws_peer->poll();
- while (ws_peer->is_connected_to_host() && ws_peer->get_available_packet_count() > 0 && in_queue.size() < max_queued_messages) {
+ while (ws_peer->get_ready_state() == WebSocketPeer::STATE_OPEN && ws_peer->get_available_packet_count() > 0 && in_queue.size() < max_queued_messages) {
Variant var;
Error err = ws_peer->get_var(var);
ERR_CONTINUE(err != OK);
@@ -64,7 +72,7 @@ void RemoteDebuggerPeerWebSocket::poll() {
in_queue.push_back(var);
}
- while (ws_peer->is_connected_to_host() && out_queue.size() > 0) {
+ while (ws_peer->get_ready_state() == WebSocketPeer::STATE_OPEN && out_queue.size() > 0) {
Array var = out_queue[0];
Error err = ws_peer->put_var(var);
ERR_BREAK(err != OK); // Peer buffer full?
@@ -73,7 +81,7 @@ void RemoteDebuggerPeerWebSocket::poll() {
}
int RemoteDebuggerPeerWebSocket::get_max_message_size() const {
- return 8 << 20; // 8 Mib
+ return ws_peer->get_max_packet_size();
}
bool RemoteDebuggerPeerWebSocket::has_message() {
@@ -99,7 +107,6 @@ void RemoteDebuggerPeerWebSocket::close() {
if (ws_peer.is_valid()) {
ws_peer.unref();
}
- ws_client->disconnect_from_host();
}
bool RemoteDebuggerPeerWebSocket::can_block() const {
@@ -111,14 +118,13 @@ bool RemoteDebuggerPeerWebSocket::can_block() const {
}
RemoteDebuggerPeerWebSocket::RemoteDebuggerPeerWebSocket(Ref<WebSocketPeer> p_peer) {
-#ifdef WEB_ENABLED
- ws_client = Ref<WebSocketClient>(memnew(EMWSClient));
-#else
- ws_client = Ref<WebSocketClient>(memnew(WSLClient));
-#endif
max_queued_messages = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages");
- ws_client->set_buffers(8192, max_queued_messages, 8192, max_queued_messages);
ws_peer = p_peer;
+ if (ws_peer.is_valid()) {
+ ws_peer->set_max_queued_packets(max_queued_messages);
+ ws_peer->set_inbound_buffer_size((1 << 23) - 1);
+ ws_peer->set_outbound_buffer_size((1 << 23) - 1);
+ }
}
RemoteDebuggerPeer *RemoteDebuggerPeerWebSocket::create(const String &p_uri) {
diff --git a/modules/websocket/remote_debugger_peer_websocket.h b/modules/websocket/remote_debugger_peer_websocket.h
index 0292de68ad..4d496ae891 100644
--- a/modules/websocket/remote_debugger_peer_websocket.h
+++ b/modules/websocket/remote_debugger_peer_websocket.h
@@ -33,14 +33,9 @@
#include "core/debugger/remote_debugger_peer.h"
-#ifdef WEB_ENABLED
-#include "emws_client.h"
-#else
-#include "wsl_client.h"
-#endif
+#include "websocket_peer.h"
class RemoteDebuggerPeerWebSocket : public RemoteDebuggerPeer {
- Ref<WebSocketClient> ws_client;
Ref<WebSocketPeer> ws_peer;
List<Array> in_queue;
List<Array> out_queue;
diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp
deleted file mode 100644
index 0b2d5d1918..0000000000
--- a/modules/websocket/websocket_client.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-/*************************************************************************/
-/* websocket_client.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 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. */
-/*************************************************************************/
-
-#include "websocket_client.h"
-
-GDCINULL(WebSocketClient);
-
-WebSocketClient::WebSocketClient() {
-}
-
-WebSocketClient::~WebSocketClient() {
-}
-
-Error WebSocketClient::connect_to_url(String p_url, const Vector<String> p_protocols, bool gd_mp_api, const Vector<String> p_custom_headers) {
- _is_multiplayer = gd_mp_api;
-
- String host = p_url;
- String path;
- String scheme;
- int port = 0;
- Error err = p_url.parse_url(scheme, host, port, path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);
-
- bool tls = false;
- if (scheme == "wss://") {
- tls = true;
- }
- if (port == 0) {
- port = tls ? 443 : 80;
- }
- if (path.is_empty()) {
- path = "/";
- }
- return connect_to_host(host, path, port, tls, p_protocols, p_custom_headers);
-}
-
-void WebSocketClient::set_verify_tls_enabled(bool p_verify_tls) {
- verify_tls = p_verify_tls;
-}
-
-bool WebSocketClient::is_verify_tls_enabled() const {
- return verify_tls;
-}
-
-Ref<X509Certificate> WebSocketClient::get_trusted_tls_certificate() const {
- return tls_cert;
-}
-
-void WebSocketClient::set_trusted_tls_certificate(Ref<X509Certificate> p_cert) {
- ERR_FAIL_COND(get_connection_status() != CONNECTION_DISCONNECTED);
- tls_cert = p_cert;
-}
-
-bool WebSocketClient::is_server() const {
- return false;
-}
-
-void WebSocketClient::_on_peer_packet() {
- if (_is_multiplayer) {
- _process_multiplayer(get_peer(1), 1);
- } else {
- emit_signal(SNAME("data_received"));
- }
-}
-
-void WebSocketClient::_on_connect(String p_protocol) {
- if (_is_multiplayer) {
- // need to wait for ID confirmation...
- } else {
- emit_signal(SNAME("connection_established"), p_protocol);
- }
-}
-
-void WebSocketClient::_on_close_request(int p_code, String p_reason) {
- emit_signal(SNAME("server_close_request"), p_code, p_reason);
-}
-
-void WebSocketClient::_on_disconnect(bool p_was_clean) {
- if (_is_multiplayer) {
- emit_signal(SNAME("connection_failed"));
- } else {
- emit_signal(SNAME("connection_closed"), p_was_clean);
- }
-}
-
-void WebSocketClient::_on_error() {
- if (_is_multiplayer) {
- emit_signal(SNAME("connection_failed"));
- } else {
- emit_signal(SNAME("connection_error"));
- }
-}
-
-void WebSocketClient::_bind_methods() {
- ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api", "custom_headers"), &WebSocketClient::connect_to_url, DEFVAL(Vector<String>()), DEFVAL(false), DEFVAL(Vector<String>()));
- ClassDB::bind_method(D_METHOD("disconnect_from_host", "code", "reason"), &WebSocketClient::disconnect_from_host, DEFVAL(1000), DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_connected_host"), &WebSocketClient::get_connected_host);
- ClassDB::bind_method(D_METHOD("get_connected_port"), &WebSocketClient::get_connected_port);
- ClassDB::bind_method(D_METHOD("set_verify_tls_enabled", "enabled"), &WebSocketClient::set_verify_tls_enabled);
- ClassDB::bind_method(D_METHOD("is_verify_tls_enabled"), &WebSocketClient::is_verify_tls_enabled);
-
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "verify_tls", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_verify_tls_enabled", "is_verify_tls_enabled");
-
- ClassDB::bind_method(D_METHOD("get_trusted_tls_certificate"), &WebSocketClient::get_trusted_tls_certificate);
- ClassDB::bind_method(D_METHOD("set_trusted_tls_certificate", "cert"), &WebSocketClient::set_trusted_tls_certificate);
-
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trusted_tls_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_trusted_tls_certificate", "get_trusted_tls_certificate");
-
- ADD_SIGNAL(MethodInfo("data_received"));
- ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol")));
- ADD_SIGNAL(MethodInfo("server_close_request", PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason")));
- ADD_SIGNAL(MethodInfo("connection_closed", PropertyInfo(Variant::BOOL, "was_clean_close")));
- ADD_SIGNAL(MethodInfo("connection_error"));
-}
diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp
index 7a3bbf1c47..00b96ec587 100644
--- a/modules/websocket/websocket_multiplayer_peer.cpp
+++ b/modules/websocket/websocket_multiplayer_peer.cpp
@@ -33,70 +33,118 @@
#include "core/os/os.h"
WebSocketMultiplayerPeer::WebSocketMultiplayerPeer() {
+ peer_config = Ref<WebSocketPeer>(WebSocketPeer::create());
}
WebSocketMultiplayerPeer::~WebSocketMultiplayerPeer() {
_clear();
}
+Ref<WebSocketPeer> WebSocketMultiplayerPeer::_create_peer() {
+ Ref<WebSocketPeer> peer = Ref<WebSocketPeer>(WebSocketPeer::create());
+ peer->set_supported_protocols(get_supported_protocols());
+ peer->set_handshake_headers(get_handshake_headers());
+ peer->set_inbound_buffer_size(get_inbound_buffer_size());
+ peer->set_outbound_buffer_size(get_outbound_buffer_size());
+ peer->set_max_queued_packets(get_max_queued_packets());
+ return peer;
+}
+
void WebSocketMultiplayerPeer::_clear() {
- _peer_map.clear();
- if (_current_packet.data != nullptr) {
- memfree(_current_packet.data);
+ connection_status = CONNECTION_DISCONNECTED;
+ unique_id = 0;
+ peers_map.clear();
+ use_tls = false;
+ tcp_server.unref();
+ pending_peers.clear();
+ tls_certificate.unref();
+ tls_key.unref();
+ if (current_packet.data != nullptr) {
+ memfree(current_packet.data);
}
- for (Packet &E : _incoming_packets) {
+ for (Packet &E : incoming_packets) {
memfree(E.data);
E.data = nullptr;
}
- _incoming_packets.clear();
+ incoming_packets.clear();
}
void WebSocketMultiplayerPeer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_buffers", "input_buffer_size_kb", "input_max_packets", "output_buffer_size_kb", "output_max_packets"), &WebSocketMultiplayerPeer::set_buffers);
+ ClassDB::bind_method(D_METHOD("create_client", "url", "verify_tls", "tls_certificate"), &WebSocketMultiplayerPeer::create_client, DEFVAL(true), DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_key", "tls_certificate"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref<CryptoKey>()), DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_method(D_METHOD("close"), &WebSocketMultiplayerPeer::close);
+
ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer);
+ ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketMultiplayerPeer::get_peer_address);
+ ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketMultiplayerPeer::get_peer_port);
+ ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketMultiplayerPeer::disconnect_peer, DEFVAL(1000), DEFVAL(""));
+
+ ClassDB::bind_method(D_METHOD("get_supported_protocols"), &WebSocketMultiplayerPeer::get_supported_protocols);
+ ClassDB::bind_method(D_METHOD("set_supported_protocols", "protocols"), &WebSocketMultiplayerPeer::set_supported_protocols);
+
+ ClassDB::bind_method(D_METHOD("get_handshake_headers"), &WebSocketMultiplayerPeer::get_handshake_headers);
+ ClassDB::bind_method(D_METHOD("set_handshake_headers", "protocols"), &WebSocketMultiplayerPeer::set_handshake_headers);
+
+ ClassDB::bind_method(D_METHOD("get_inbound_buffer_size"), &WebSocketMultiplayerPeer::get_inbound_buffer_size);
+ ClassDB::bind_method(D_METHOD("set_inbound_buffer_size", "buffer_size"), &WebSocketMultiplayerPeer::set_inbound_buffer_size);
+
+ ClassDB::bind_method(D_METHOD("get_outbound_buffer_size"), &WebSocketMultiplayerPeer::get_outbound_buffer_size);
+ ClassDB::bind_method(D_METHOD("set_outbound_buffer_size", "buffer_size"), &WebSocketMultiplayerPeer::set_outbound_buffer_size);
- ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "peer_source")));
+ ClassDB::bind_method(D_METHOD("get_handshake_timeout"), &WebSocketMultiplayerPeer::get_handshake_timeout);
+ ClassDB::bind_method(D_METHOD("set_handshake_timeout", "timeout"), &WebSocketMultiplayerPeer::set_handshake_timeout);
+
+ ClassDB::bind_method(D_METHOD("set_max_queued_packets", "max_queued_packets"), &WebSocketMultiplayerPeer::set_max_queued_packets);
+ ClassDB::bind_method(D_METHOD("get_max_queued_packets"), &WebSocketMultiplayerPeer::get_max_queued_packets);
+
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "supported_protocols"), "set_supported_protocols", "get_supported_protocols");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "handshake_headers"), "set_handshake_headers", "get_handshake_headers");
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "inbound_buffer_size"), "set_inbound_buffer_size", "get_inbound_buffer_size");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "outbound_buffer_size"), "set_outbound_buffer_size", "get_outbound_buffer_size");
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "handshake_timeout"), "set_handshake_timeout", "get_handshake_timeout");
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_queued_packets"), "set_max_queued_packets", "get_max_queued_packets");
}
//
// PacketPeer
//
int WebSocketMultiplayerPeer::get_available_packet_count() const {
- ERR_FAIL_COND_V_MSG(!_is_multiplayer, 0, "Please use get_peer(ID).get_available_packet_count to get available packet count from peers when not using the MultiplayerAPI.");
-
- return _incoming_packets.size();
+ return incoming_packets.size();
}
Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
- ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).get_packet/var to communicate with peers when not using the MultiplayerAPI.");
+ ERR_FAIL_COND_V(get_connection_status() != CONNECTION_CONNECTED, ERR_UNCONFIGURED);
r_buffer_size = 0;
- if (_current_packet.data != nullptr) {
- memfree(_current_packet.data);
- _current_packet.data = nullptr;
+ if (current_packet.data != nullptr) {
+ memfree(current_packet.data);
+ current_packet.data = nullptr;
}
- ERR_FAIL_COND_V(_incoming_packets.size() == 0, ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V(incoming_packets.size() == 0, ERR_UNAVAILABLE);
- _current_packet = _incoming_packets.front()->get();
- _incoming_packets.pop_front();
+ current_packet = incoming_packets.front()->get();
+ incoming_packets.pop_front();
- *r_buffer = _current_packet.data;
- r_buffer_size = _current_packet.size;
+ *r_buffer = current_packet.data;
+ r_buffer_size = current_packet.size;
return OK;
}
Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
- ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).put_packet/var to communicate with peers when not using the MultiplayerAPI.");
+ ERR_FAIL_COND_V(get_connection_status() != CONNECTION_CONNECTED, ERR_UNCONFIGURED);
- Vector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), _target_peer, p_buffer, p_buffer_size);
+ Vector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), target_peer, p_buffer, p_buffer_size);
if (is_server()) {
- return _server_relay(1, _target_peer, &(buffer.ptr()[0]), buffer.size());
+ return _server_relay(1, target_peer, &(buffer.ptr()[0]), buffer.size());
} else {
return get_peer(1)->put_packet(&(buffer.ptr()[0]), buffer.size());
}
@@ -106,23 +154,225 @@ Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer
// MultiplayerPeer
//
void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) {
- _target_peer = p_target_peer;
+ target_peer = p_target_peer;
}
int WebSocketMultiplayerPeer::get_packet_peer() const {
- ERR_FAIL_COND_V_MSG(!_is_multiplayer, 1, "This function is not available when not using the MultiplayerAPI.");
- ERR_FAIL_COND_V(_incoming_packets.size() == 0, 1);
+ ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
- return _incoming_packets.front()->get().source;
+ return incoming_packets.front()->get().source;
}
int WebSocketMultiplayerPeer::get_unique_id() const {
- return _peer_id;
+ return unique_id;
+}
+
+int WebSocketMultiplayerPeer::get_max_packet_size() const {
+ return get_outbound_buffer_size() - PROTO_SIZE;
+}
+
+Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref<CryptoKey> p_tls_key, Ref<X509Certificate> p_tls_certificate) {
+ ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE);
+ _clear();
+ tcp_server.instantiate();
+ Error err = tcp_server->listen(p_port, p_bind_ip);
+ if (err != OK) {
+ tcp_server.unref();
+ return err;
+ }
+ unique_id = 1;
+ connection_status = CONNECTION_CONNECTED;
+ // TLS config
+ tls_key = p_tls_key;
+ tls_certificate = p_tls_certificate;
+ if (tls_key.is_valid() && tls_certificate.is_valid()) {
+ use_tls = true;
+ }
+ return OK;
+}
+
+Error WebSocketMultiplayerPeer::create_client(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate) {
+ ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE);
+ _clear();
+ Ref<WebSocketPeer> peer = _create_peer();
+ Error err = peer->connect_to_url(p_url, p_verify_tls, p_tls_certificate);
+ if (err != OK) {
+ return err;
+ }
+ PendingPeer pending;
+ pending.time = OS::get_singleton()->get_ticks_msec();
+ pending_peers[1] = pending;
+ peers_map[1] = peer;
+ connection_status = CONNECTION_CONNECTING;
+ return OK;
+}
+
+bool WebSocketMultiplayerPeer::is_server() const {
+ return tcp_server.is_valid();
+}
+
+void WebSocketMultiplayerPeer::_poll_client() {
+ ERR_FAIL_COND(connection_status == CONNECTION_DISCONNECTED); // Bug.
+ ERR_FAIL_COND(!peers_map.has(1) || peers_map[1].is_null()); // Bug.
+ Ref<WebSocketPeer> peer = peers_map[1];
+ peer->poll(); // Update state and fetch packets.
+ WebSocketPeer::State ready_state = peer->get_ready_state();
+ if (ready_state == WebSocketPeer::STATE_OPEN) {
+ while (peer->get_available_packet_count()) {
+ _process_multiplayer(peer, 1);
+ }
+ } else if (peer->get_ready_state() == WebSocketPeer::STATE_CLOSED) {
+ if (connection_status == CONNECTION_CONNECTED) {
+ emit_signal(SNAME("server_disconnected"));
+ } else {
+ emit_signal(SNAME("connection_failed"));
+ }
+ _clear();
+ return;
+ }
+ if (connection_status == CONNECTION_CONNECTING) {
+ // Still connecting
+ ERR_FAIL_COND(!pending_peers.has(1)); // Bug.
+ if (OS::get_singleton()->get_ticks_msec() - pending_peers[1].time > handshake_timeout) {
+ print_verbose(vformat("WebSocket handshake timed out after %.3f seconds.", handshake_timeout * 0.001));
+ emit_signal(SNAME("connection_failed"));
+ _clear();
+ return;
+ }
+ }
+}
+
+void WebSocketMultiplayerPeer::_poll_server() {
+ ERR_FAIL_COND(connection_status != CONNECTION_CONNECTED); // Bug.
+ ERR_FAIL_COND(tcp_server.is_null() || !tcp_server->is_listening()); // Bug.
+
+ // Accept new connections.
+ if (!is_refusing_new_connections() && tcp_server->is_connection_available()) {
+ PendingPeer peer;
+ peer.time = OS::get_singleton()->get_ticks_msec();
+ peer.tcp = tcp_server->take_connection();
+ peer.connection = peer.tcp;
+ pending_peers[generate_unique_id()] = peer;
+ }
+
+ // Process pending peers.
+ HashSet<int> to_remove;
+ for (KeyValue<int, PendingPeer> &E : pending_peers) {
+ PendingPeer &peer = E.value;
+ int id = E.key;
+
+ if (OS::get_singleton()->get_ticks_msec() - peer.time > handshake_timeout) {
+ print_verbose(vformat("WebSocket handshake timed out after %.3f seconds.", handshake_timeout * 0.001));
+ to_remove.insert(id);
+ continue;
+ }
+
+ if (peer.ws.is_valid()) {
+ peer.ws->poll();
+ WebSocketPeer::State state = peer.ws->get_ready_state();
+ if (state == WebSocketPeer::STATE_OPEN) {
+ // Connected.
+ to_remove.insert(id);
+ if (is_refusing_new_connections()) {
+ // The user does not want new connections, dropping it.
+ continue;
+ }
+ peers_map[id] = peer.ws;
+ _send_ack(peer.ws, id);
+ emit_signal("peer_connected", id);
+ continue;
+ } else if (state == WebSocketPeer::STATE_CONNECTING) {
+ continue; // Still connecting.
+ }
+ to_remove.insert(id); // Error.
+ continue;
+ }
+ if (peer.tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
+ to_remove.insert(id); // Error.
+ continue;
+ }
+ if (!use_tls) {
+ peer.ws = _create_peer();
+ peer.ws->accept_stream(peer.tcp);
+ continue;
+ } else {
+ if (peer.connection == peer.tcp) {
+ Ref<StreamPeerTLS> tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
+ Error err = tls->accept_stream(peer.tcp, tls_key, tls_certificate);
+ if (err != OK) {
+ to_remove.insert(id);
+ continue;
+ }
+ }
+ Ref<StreamPeerTLS> tls = static_cast<Ref<StreamPeerTLS>>(peer.connection);
+ tls->poll();
+ if (tls->get_status() == StreamPeerTLS::STATUS_CONNECTED) {
+ peer.ws = _create_peer();
+ peer.ws->accept_stream(peer.connection);
+ continue;
+ } else if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) {
+ // Still connecting.
+ continue;
+ } else {
+ // Error
+ to_remove.insert(id);
+ }
+ }
+ }
+
+ // Remove disconnected pending peers.
+ for (const int &pid : to_remove) {
+ pending_peers.erase(pid);
+ }
+ to_remove.clear();
+
+ // Process connected peers.
+ for (KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
+ Ref<WebSocketPeer> ws = E.value;
+ int id = E.key;
+ ws->poll();
+ if (ws->get_ready_state() != WebSocketPeer::STATE_OPEN) {
+ to_remove.insert(id); // Disconnected.
+ continue;
+ }
+ // Fetch packets
+ int pkts = ws->get_available_packet_count();
+ while (pkts && ws->get_ready_state() == WebSocketPeer::STATE_OPEN) {
+ _process_multiplayer(ws, id);
+ pkts--;
+ }
+ }
+
+ // Remove disconnected peers.
+ for (const int &pid : to_remove) {
+ emit_signal(SNAME("peer_disconnected"), pid);
+ peers_map.erase(pid);
+ }
+}
+
+void WebSocketMultiplayerPeer::poll() {
+ if (connection_status == CONNECTION_DISCONNECTED) {
+ return;
+ }
+ if (is_server()) {
+ _poll_server();
+ } else {
+ _poll_client();
+ }
+}
+
+MultiplayerPeer::ConnectionStatus WebSocketMultiplayerPeer::get_connection_status() const {
+ return connection_status;
+}
+
+Ref<WebSocketPeer> WebSocketMultiplayerPeer::get_peer(int p_id) const {
+ ERR_FAIL_COND_V(!peers_map.has(p_id), Ref<WebSocketPeer>());
+ return peers_map[p_id];
}
void WebSocketMultiplayerPeer::_send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id) {
ERR_FAIL_COND(!p_peer.is_valid());
- ERR_FAIL_COND(!p_peer->is_connected_to_host());
+ ERR_FAIL_COND(p_peer->get_ready_state() != WebSocketPeer::STATE_OPEN);
Vector<uint8_t> message = _make_pkt(p_type, 1, 0, (uint8_t *)&p_peer_id, 4);
p_peer->put_packet(&(message.ptr()[0]), message.size());
@@ -141,31 +391,34 @@ Vector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint8_t p_type, int32_t p_fr
return out;
}
-void WebSocketMultiplayerPeer::_send_add(int32_t p_peer_id) {
+void WebSocketMultiplayerPeer::_send_ack(Ref<WebSocketPeer> p_peer, int32_t p_peer_id) {
+ ERR_FAIL_COND(p_peer.is_null());
// First of all, confirm the ID!
- _send_sys(get_peer(p_peer_id), SYS_ID, p_peer_id);
+ _send_sys(p_peer, SYS_ID, p_peer_id);
// Then send the server peer (which will trigger connection_succeded in client)
- _send_sys(get_peer(p_peer_id), SYS_ADD, 1);
+ _send_sys(p_peer, SYS_ADD, 1);
+
+ for (const KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
+ ERR_CONTINUE(E.value.is_null());
- for (const KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) {
int32_t id = E.key;
if (p_peer_id == id) {
continue; // Skip the newly added peer (already confirmed)
}
// Send new peer to others
- _send_sys(get_peer(id), SYS_ADD, p_peer_id);
+ _send_sys(E.value, SYS_ADD, p_peer_id);
// Send others to new peer
- _send_sys(get_peer(p_peer_id), SYS_ADD, id);
+ _send_sys(E.value, SYS_ADD, id);
}
}
void WebSocketMultiplayerPeer::_send_del(int32_t p_peer_id) {
- for (const KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) {
+ for (const KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
int32_t id = E.key;
if (p_peer_id != id) {
- _send_sys(get_peer(id), SYS_DEL, p_peer_id);
+ _send_sys(E.value, SYS_DEL, p_peer_id);
}
}
}
@@ -177,8 +430,7 @@ void WebSocketMultiplayerPeer::_store_pkt(int32_t p_source, int32_t p_dest, cons
packet.source = p_source;
packet.destination = p_dest;
memcpy(packet.data, &p_data[PROTO_SIZE], p_data_size);
- _incoming_packets.push_back(packet);
- emit_signal(SNAME("peer_packet"), p_source);
+ incoming_packets.push_back(packet);
}
Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size) {
@@ -186,7 +438,7 @@ Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, cons
return OK; // Will not send to self
} else if (p_to == 0) {
- for (KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) {
+ for (KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
if (E.key != p_from) {
E.value->put_packet(p_buffer, p_buffer_size);
}
@@ -194,7 +446,7 @@ Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, cons
return OK; // Sent to all but sender
} else if (p_to < 0) {
- for (KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) {
+ for (KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
if (E.key != p_from && E.key != -p_to) {
E.value->put_packet(p_buffer, p_buffer_size);
}
@@ -237,26 +489,24 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u
ERR_FAIL_COND(type != SYS_NONE); // Only server sends sys messages
ERR_FAIL_COND(from != p_peer_id); // Someone is cheating
- if (to == 1) { // This is for the server
-
+ if (to == 1) {
+ // This is for the server
_store_pkt(from, to, in_buffer, data_size);
} else if (to == 0) {
// Broadcast, for us too
_store_pkt(from, to, in_buffer, data_size);
- } else if (to < 0) {
+ } else if (to < -1) {
// All but one, for us if not excluded
- if (_peer_id != -(int32_t)p_peer_id) {
- _store_pkt(from, to, in_buffer, data_size);
- }
+ _store_pkt(from, to, in_buffer, data_size);
}
// Relay if needed (i.e. "to" includes a peer that is not the server)
_server_relay(from, to, in_buffer, size);
} else {
- if (type == SYS_NONE) { // Payload message
-
+ if (type == SYS_NONE) {
+ // Payload message
_store_pkt(from, to, in_buffer, data_size);
return;
}
@@ -268,7 +518,12 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u
switch (type) {
case SYS_ADD: // Add peer
- _peer_map[id] = Ref<WebSocketPeer>();
+ if (id != 1) {
+ peers_map[id] = Ref<WebSocketPeer>();
+ } else {
+ pending_peers.clear();
+ connection_status = CONNECTION_CONNECTED;
+ }
emit_signal(SNAME("peer_connected"), id);
if (id == 1) { // We just connected to the server
emit_signal(SNAME("connection_succeeded"));
@@ -276,11 +531,11 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u
break;
case SYS_DEL: // Remove peer
- _peer_map.erase(id);
emit_signal(SNAME("peer_disconnected"), id);
+ peers_map.erase(id);
break;
case SYS_ID: // Hello, server assigned ID
- _peer_id = id;
+ unique_id = id;
break;
default:
ERR_FAIL_MSG("Invalid multiplayer message.");
@@ -288,3 +543,71 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u
}
}
}
+
+void WebSocketMultiplayerPeer::set_supported_protocols(const Vector<String> &p_protocols) {
+ peer_config->set_supported_protocols(p_protocols);
+}
+
+Vector<String> WebSocketMultiplayerPeer::get_supported_protocols() const {
+ return peer_config->get_supported_protocols();
+}
+
+void WebSocketMultiplayerPeer::set_handshake_headers(const Vector<String> &p_headers) {
+ peer_config->set_handshake_headers(p_headers);
+}
+
+Vector<String> WebSocketMultiplayerPeer::get_handshake_headers() const {
+ return peer_config->get_handshake_headers();
+}
+
+void WebSocketMultiplayerPeer::set_outbound_buffer_size(int p_buffer_size) {
+ peer_config->set_outbound_buffer_size(p_buffer_size);
+}
+
+int WebSocketMultiplayerPeer::get_outbound_buffer_size() const {
+ return peer_config->get_outbound_buffer_size();
+}
+
+void WebSocketMultiplayerPeer::set_inbound_buffer_size(int p_buffer_size) {
+ peer_config->set_inbound_buffer_size(p_buffer_size);
+}
+
+int WebSocketMultiplayerPeer::get_inbound_buffer_size() const {
+ return peer_config->get_inbound_buffer_size();
+}
+
+void WebSocketMultiplayerPeer::set_max_queued_packets(int p_max_queued_packets) {
+ peer_config->set_max_queued_packets(p_max_queued_packets);
+}
+
+int WebSocketMultiplayerPeer::get_max_queued_packets() const {
+ return peer_config->get_max_queued_packets();
+}
+
+float WebSocketMultiplayerPeer::get_handshake_timeout() const {
+ return handshake_timeout / 1000.0;
+}
+
+void WebSocketMultiplayerPeer::set_handshake_timeout(float p_timeout) {
+ ERR_FAIL_COND(p_timeout <= 0.0);
+ handshake_timeout = p_timeout * 1000;
+}
+
+IPAddress WebSocketMultiplayerPeer::get_peer_address(int p_peer_id) const {
+ ERR_FAIL_COND_V(!peers_map.has(p_peer_id), IPAddress());
+ return peers_map[p_peer_id]->get_connected_host();
+}
+
+int WebSocketMultiplayerPeer::get_peer_port(int p_peer_id) const {
+ ERR_FAIL_COND_V(!peers_map.has(p_peer_id), 0);
+ return peers_map[p_peer_id]->get_connected_port();
+}
+
+void WebSocketMultiplayerPeer::disconnect_peer(int p_peer_id, int p_code, String p_reason) {
+ ERR_FAIL_COND(!peers_map.has(p_peer_id));
+ peers_map[p_peer_id]->close(p_code, p_reason);
+}
+
+void WebSocketMultiplayerPeer::close() {
+ _clear();
+}
diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h
index 3259e78b3b..8e7b118faa 100644
--- a/modules/websocket/websocket_multiplayer_peer.h
+++ b/modules/websocket/websocket_multiplayer_peer.h
@@ -32,6 +32,8 @@
#define WEBSOCKET_MULTIPLAYER_PEER_H
#include "core/error/error_list.h"
+#include "core/io/stream_peer_tls.h"
+#include "core/io/tcp_server.h"
#include "core/templates/list.h"
#include "scene/main/multiplayer_peer.h"
#include "websocket_peer.h"
@@ -43,6 +45,7 @@ private:
Vector<uint8_t> _make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size);
void _store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size);
Error _server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size);
+ Ref<WebSocketPeer> _create_peer();
protected:
enum {
@@ -61,19 +64,40 @@ protected:
uint32_t size = 0;
};
- List<Packet> _incoming_packets;
- HashMap<int, Ref<WebSocketPeer>> _peer_map;
- Packet _current_packet;
+ struct PendingPeer {
+ uint64_t time = 0;
+ Ref<StreamPeerTCP> tcp;
+ Ref<StreamPeer> connection;
+ Ref<WebSocketPeer> ws;
+ };
+
+ uint64_t handshake_timeout = 3000;
+ Ref<WebSocketPeer> peer_config;
+ HashMap<int, PendingPeer> pending_peers;
+ Ref<TCPServer> tcp_server;
+ bool use_tls = false;
+ Ref<X509Certificate> tls_certificate;
+ Ref<CryptoKey> tls_key;
+
+ ConnectionStatus connection_status = CONNECTION_DISCONNECTED;
- bool _is_multiplayer = false;
- int _target_peer = 0;
- int _peer_id = 0;
+ List<Packet> incoming_packets;
+ HashMap<int, Ref<WebSocketPeer>> peers_map;
+ Packet current_packet;
+
+ int target_peer = 0;
+ int unique_id = 0;
static void _bind_methods();
- void _send_add(int32_t p_peer_id);
+ void _send_ack(Ref<WebSocketPeer> p_peer, int32_t p_peer_id);
void _send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id);
void _send_del(int32_t p_peer_id);
+ void _process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id);
+
+ void _poll_client();
+ void _poll_server();
+ void _clear();
public:
/* MultiplayerPeer */
@@ -81,17 +105,44 @@ public:
int get_packet_peer() const override;
int get_unique_id() const override;
+ virtual int get_max_packet_size() const override;
+ virtual bool is_server() const override;
+ virtual void poll() override;
+ virtual ConnectionStatus get_connection_status() const override;
+
/* PacketPeer */
virtual int get_available_packet_count() const override;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
/* WebSocketPeer */
- virtual Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) = 0;
- virtual Ref<WebSocketPeer> get_peer(int p_peer_id) const = 0;
+ virtual Ref<WebSocketPeer> get_peer(int p_peer_id) const;
- void _process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id);
- void _clear();
+ Error create_client(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate);
+ Error create_server(int p_port, IPAddress p_bind_ip, Ref<CryptoKey> p_tls_key, Ref<X509Certificate> p_tls_certificate);
+
+ void set_supported_protocols(const Vector<String> &p_protocols);
+ Vector<String> get_supported_protocols() const;
+
+ void set_handshake_headers(const Vector<String> &p_headers);
+ Vector<String> get_handshake_headers() const;
+
+ void set_outbound_buffer_size(int p_buffer_size);
+ int get_outbound_buffer_size() const;
+
+ void set_inbound_buffer_size(int p_buffer_size);
+ int get_inbound_buffer_size() const;
+
+ float get_handshake_timeout() const;
+ void set_handshake_timeout(float p_timeout);
+
+ IPAddress get_peer_address(int p_peer_id) const;
+ int get_peer_port(int p_peer_id) const;
+ void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = "");
+ void close();
+
+ void set_max_queued_packets(int p_max_queued_packets);
+ int get_max_queued_packets() const;
WebSocketMultiplayerPeer();
~WebSocketMultiplayerPeer();
diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp
index a0af9303b8..b46b20bef2 100644
--- a/modules/websocket/websocket_peer.cpp
+++ b/modules/websocket/websocket_peer.cpp
@@ -30,7 +30,7 @@
#include "websocket_peer.h"
-GDCINULL(WebSocketPeer);
+WebSocketPeer *(*WebSocketPeer::_create)() = nullptr;
WebSocketPeer::WebSocketPeer() {
}
@@ -39,16 +39,115 @@ WebSocketPeer::~WebSocketPeer() {
}
void WebSocketPeer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("get_write_mode"), &WebSocketPeer::get_write_mode);
- ClassDB::bind_method(D_METHOD("set_write_mode", "mode"), &WebSocketPeer::set_write_mode);
- ClassDB::bind_method(D_METHOD("is_connected_to_host"), &WebSocketPeer::is_connected_to_host);
+ ClassDB::bind_method(D_METHOD("connect_to_url", "url", "verify_tls", "trusted_tls_certificate"), &WebSocketPeer::connect_to_url, DEFVAL(true), DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_method(D_METHOD("accept_stream", "stream"), &WebSocketPeer::accept_stream);
+ ClassDB::bind_method(D_METHOD("send", "message", "write_mode"), &WebSocketPeer::_send_bind, DEFVAL(WRITE_MODE_BINARY));
+ ClassDB::bind_method(D_METHOD("send_text", "message"), &WebSocketPeer::send_text);
ClassDB::bind_method(D_METHOD("was_string_packet"), &WebSocketPeer::was_string_packet);
+ ClassDB::bind_method(D_METHOD("poll"), &WebSocketPeer::poll);
ClassDB::bind_method(D_METHOD("close", "code", "reason"), &WebSocketPeer::close, DEFVAL(1000), DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_connected_host"), &WebSocketPeer::get_connected_host);
ClassDB::bind_method(D_METHOD("get_connected_port"), &WebSocketPeer::get_connected_port);
+ ClassDB::bind_method(D_METHOD("get_selected_protocol"), &WebSocketPeer::get_selected_protocol);
+ ClassDB::bind_method(D_METHOD("get_requested_url"), &WebSocketPeer::get_requested_url);
ClassDB::bind_method(D_METHOD("set_no_delay", "enabled"), &WebSocketPeer::set_no_delay);
ClassDB::bind_method(D_METHOD("get_current_outbound_buffered_amount"), &WebSocketPeer::get_current_outbound_buffered_amount);
+ ClassDB::bind_method(D_METHOD("get_ready_state"), &WebSocketPeer::get_ready_state);
+ ClassDB::bind_method(D_METHOD("get_close_code"), &WebSocketPeer::get_close_code);
+ ClassDB::bind_method(D_METHOD("get_close_reason"), &WebSocketPeer::get_close_reason);
+
+ ClassDB::bind_method(D_METHOD("get_supported_protocols"), &WebSocketPeer::_get_supported_protocols);
+ ClassDB::bind_method(D_METHOD("set_supported_protocols", "protocols"), &WebSocketPeer::set_supported_protocols);
+ ClassDB::bind_method(D_METHOD("get_handshake_headers"), &WebSocketPeer::_get_handshake_headers);
+ ClassDB::bind_method(D_METHOD("set_handshake_headers", "protocols"), &WebSocketPeer::set_handshake_headers);
+
+ ClassDB::bind_method(D_METHOD("get_inbound_buffer_size"), &WebSocketPeer::get_inbound_buffer_size);
+ ClassDB::bind_method(D_METHOD("set_inbound_buffer_size", "buffer_size"), &WebSocketPeer::set_inbound_buffer_size);
+ ClassDB::bind_method(D_METHOD("get_outbound_buffer_size"), &WebSocketPeer::get_outbound_buffer_size);
+ ClassDB::bind_method(D_METHOD("set_outbound_buffer_size", "buffer_size"), &WebSocketPeer::set_outbound_buffer_size);
+
+ ClassDB::bind_method(D_METHOD("set_max_queued_packets", "buffer_size"), &WebSocketPeer::set_max_queued_packets);
+ ClassDB::bind_method(D_METHOD("get_max_queued_packets"), &WebSocketPeer::get_max_queued_packets);
+
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "supported_protocols"), "set_supported_protocols", "get_supported_protocols");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "handshake_headers"), "set_handshake_headers", "get_handshake_headers");
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "inbound_buffer_size"), "set_inbound_buffer_size", "get_inbound_buffer_size");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "outbound_buffer_size"), "set_outbound_buffer_size", "get_outbound_buffer_size");
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_queued_packets"), "set_max_queued_packets", "get_max_queued_packets");
+
BIND_ENUM_CONSTANT(WRITE_MODE_TEXT);
BIND_ENUM_CONSTANT(WRITE_MODE_BINARY);
+
+ BIND_ENUM_CONSTANT(STATE_CONNECTING);
+ BIND_ENUM_CONSTANT(STATE_OPEN);
+ BIND_ENUM_CONSTANT(STATE_CLOSING);
+ BIND_ENUM_CONSTANT(STATE_CLOSED);
+}
+
+Error WebSocketPeer::_send_bind(const PackedByteArray &p_message, WriteMode p_mode) {
+ return send(p_message.ptr(), p_message.size(), p_mode);
+}
+
+Error WebSocketPeer::send_text(const String &p_text) {
+ const CharString cs = p_text.utf8();
+ return send((const uint8_t *)cs.ptr(), cs.length(), WRITE_MODE_TEXT);
+}
+
+void WebSocketPeer::set_supported_protocols(const Vector<String> &p_protocols) {
+ // Strip edges from protocols.
+ supported_protocols.resize(p_protocols.size());
+ for (int i = 0; i < p_protocols.size(); i++) {
+ supported_protocols.write[i] = p_protocols[i].strip_edges();
+ }
+}
+
+const Vector<String> WebSocketPeer::get_supported_protocols() const {
+ return supported_protocols;
+}
+
+Vector<String> WebSocketPeer::_get_supported_protocols() const {
+ Vector<String> out;
+ out.append_array(supported_protocols);
+ return out;
+}
+
+void WebSocketPeer::set_handshake_headers(const Vector<String> &p_headers) {
+ handshake_headers = p_headers;
+}
+
+const Vector<String> WebSocketPeer::get_handshake_headers() const {
+ return handshake_headers;
+}
+
+Vector<String> WebSocketPeer::_get_handshake_headers() const {
+ Vector<String> out;
+ out.append_array(handshake_headers);
+ return out;
+}
+
+void WebSocketPeer::set_outbound_buffer_size(int p_buffer_size) {
+ outbound_buffer_size = p_buffer_size;
+}
+
+int WebSocketPeer::get_outbound_buffer_size() const {
+ return outbound_buffer_size;
+}
+
+void WebSocketPeer::set_inbound_buffer_size(int p_buffer_size) {
+ inbound_buffer_size = p_buffer_size;
+}
+
+int WebSocketPeer::get_inbound_buffer_size() const {
+ return inbound_buffer_size;
+}
+
+void WebSocketPeer::set_max_queued_packets(int p_max_queued_packets) {
+ max_queued_packets = p_max_queued_packets;
+}
+
+int WebSocketPeer::get_max_queued_packets() const {
+ return max_queued_packets;
}
diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h
index 22099f7258..db969dd08e 100644
--- a/modules/websocket/websocket_peer.h
+++ b/modules/websocket/websocket_peer.h
@@ -31,40 +31,97 @@
#ifndef WEBSOCKET_PEER_H
#define WEBSOCKET_PEER_H
+#include "core/crypto/crypto.h"
#include "core/error/error_list.h"
#include "core/io/packet_peer.h"
-#include "websocket_macros.h"
class WebSocketPeer : public PacketPeer {
GDCLASS(WebSocketPeer, PacketPeer);
- GDCICLASS(WebSocketPeer);
public:
+ enum State {
+ STATE_CONNECTING,
+ STATE_OPEN,
+ STATE_CLOSING,
+ STATE_CLOSED
+ };
+
enum WriteMode {
WRITE_MODE_TEXT,
WRITE_MODE_BINARY,
};
+ enum {
+ DEFAULT_BUFFER_SIZE = 65535,
+ };
+
+private:
+ virtual Error _send_bind(const PackedByteArray &p_data, WriteMode p_mode = WRITE_MODE_BINARY);
+
protected:
+ static WebSocketPeer *(*_create)();
+
static void _bind_methods();
+ Vector<String> supported_protocols;
+ Vector<String> handshake_headers;
+
+ Vector<String> _get_supported_protocols() const;
+ Vector<String> _get_handshake_headers() const;
+
+ int outbound_buffer_size = DEFAULT_BUFFER_SIZE;
+ int inbound_buffer_size = DEFAULT_BUFFER_SIZE;
+ int max_queued_packets = 2048;
+
public:
- virtual WriteMode get_write_mode() const = 0;
- virtual void set_write_mode(WriteMode p_mode) = 0;
+ static WebSocketPeer *create() {
+ if (!_create) {
+ return nullptr;
+ }
+ return _create();
+ }
+ virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) { return ERR_UNAVAILABLE; };
+ virtual Error accept_stream(Ref<StreamPeer> p_stream) = 0;
+
+ virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) = 0;
virtual void close(int p_code = 1000, String p_reason = "") = 0;
- virtual bool is_connected_to_host() const = 0;
virtual IPAddress get_connected_host() const = 0;
virtual uint16_t get_connected_port() const = 0;
virtual bool was_string_packet() const = 0;
virtual void set_no_delay(bool p_enabled) = 0;
virtual int get_current_outbound_buffered_amount() const = 0;
+ virtual String get_selected_protocol() const = 0;
+ virtual String get_requested_url() const = 0;
+
+ virtual void poll() = 0;
+ virtual State get_ready_state() const = 0;
+ virtual int get_close_code() const = 0;
+ virtual String get_close_reason() const = 0;
+
+ Error send_text(const String &p_text);
+
+ void set_supported_protocols(const Vector<String> &p_protocols);
+ const Vector<String> get_supported_protocols() const;
+
+ void set_handshake_headers(const Vector<String> &p_headers);
+ const Vector<String> get_handshake_headers() const;
+
+ void set_outbound_buffer_size(int p_buffer_size);
+ int get_outbound_buffer_size() const;
+
+ void set_inbound_buffer_size(int p_buffer_size);
+ int get_inbound_buffer_size() const;
+
+ void set_max_queued_packets(int p_max_queued_packets);
+ int get_max_queued_packets() const;
WebSocketPeer();
~WebSocketPeer();
};
VARIANT_ENUM_CAST(WebSocketPeer::WriteMode);
+VARIANT_ENUM_CAST(WebSocketPeer::State);
#endif // WEBSOCKET_PEER_H
diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp
deleted file mode 100644
index 25a6e420fc..0000000000
--- a/modules/websocket/websocket_server.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-/*************************************************************************/
-/* websocket_server.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 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. */
-/*************************************************************************/
-
-#include "websocket_server.h"
-
-GDCINULL(WebSocketServer);
-
-WebSocketServer::WebSocketServer() {
- _peer_id = 1;
- bind_ip = IPAddress("*");
-}
-
-WebSocketServer::~WebSocketServer() {
-}
-
-void WebSocketServer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("is_listening"), &WebSocketServer::is_listening);
- ClassDB::bind_method(D_METHOD("set_extra_headers", "headers"), &WebSocketServer::set_extra_headers, DEFVAL(Vector<String>()));
- ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(Vector<String>()), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("stop"), &WebSocketServer::stop);
- ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer);
- ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketServer::get_peer_address);
- ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketServer::get_peer_port);
- ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketServer::disconnect_peer, DEFVAL(1000), DEFVAL(""));
-
- ClassDB::bind_method(D_METHOD("get_bind_ip"), &WebSocketServer::get_bind_ip);
- ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &WebSocketServer::set_bind_ip);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "bind_ip"), "set_bind_ip", "get_bind_ip");
-
- ClassDB::bind_method(D_METHOD("get_private_key"), &WebSocketServer::get_private_key);
- ClassDB::bind_method(D_METHOD("set_private_key", "key"), &WebSocketServer::set_private_key);
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "private_key", PROPERTY_HINT_RESOURCE_TYPE, "CryptoKey", PROPERTY_USAGE_NONE), "set_private_key", "get_private_key");
-
- ClassDB::bind_method(D_METHOD("get_tls_certificate"), &WebSocketServer::get_tls_certificate);
- ClassDB::bind_method(D_METHOD("set_tls_certificate", "cert"), &WebSocketServer::set_tls_certificate);
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tls_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_tls_certificate", "get_tls_certificate");
-
- ClassDB::bind_method(D_METHOD("get_ca_chain"), &WebSocketServer::get_ca_chain);
- ClassDB::bind_method(D_METHOD("set_ca_chain", "ca_chain"), &WebSocketServer::set_ca_chain);
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ca_chain", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_ca_chain", "get_ca_chain");
-
- ClassDB::bind_method(D_METHOD("get_handshake_timeout"), &WebSocketServer::get_handshake_timeout);
- ClassDB::bind_method(D_METHOD("set_handshake_timeout", "timeout"), &WebSocketServer::set_handshake_timeout);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "handshake_timeout"), "set_handshake_timeout", "get_handshake_timeout");
-
- ADD_SIGNAL(MethodInfo("client_close_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason")));
- ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::BOOL, "was_clean_close")));
- ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"), PropertyInfo(Variant::STRING, "resource_name")));
- ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id")));
-}
-
-IPAddress WebSocketServer::get_bind_ip() const {
- return bind_ip;
-}
-
-void WebSocketServer::set_bind_ip(const IPAddress &p_bind_ip) {
- ERR_FAIL_COND(is_listening());
- ERR_FAIL_COND(!p_bind_ip.is_valid() && !p_bind_ip.is_wildcard());
- bind_ip = p_bind_ip;
-}
-
-Ref<CryptoKey> WebSocketServer::get_private_key() const {
- return private_key;
-}
-
-void WebSocketServer::set_private_key(Ref<CryptoKey> p_key) {
- ERR_FAIL_COND(is_listening());
- private_key = p_key;
-}
-
-Ref<X509Certificate> WebSocketServer::get_tls_certificate() const {
- return tls_cert;
-}
-
-void WebSocketServer::set_tls_certificate(Ref<X509Certificate> p_cert) {
- ERR_FAIL_COND(is_listening());
- tls_cert = p_cert;
-}
-
-Ref<X509Certificate> WebSocketServer::get_ca_chain() const {
- return ca_chain;
-}
-
-void WebSocketServer::set_ca_chain(Ref<X509Certificate> p_ca_chain) {
- ERR_FAIL_COND(is_listening());
- ca_chain = p_ca_chain;
-}
-
-float WebSocketServer::get_handshake_timeout() const {
- return handshake_timeout / 1000.0;
-}
-
-void WebSocketServer::set_handshake_timeout(float p_timeout) {
- ERR_FAIL_COND(p_timeout <= 0.0);
- handshake_timeout = p_timeout * 1000;
-}
-
-MultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const {
- if (is_listening()) {
- return CONNECTION_CONNECTED;
- }
-
- return CONNECTION_DISCONNECTED;
-}
-
-bool WebSocketServer::is_server() const {
- return true;
-}
-
-void WebSocketServer::_on_peer_packet(int32_t p_peer_id) {
- if (_is_multiplayer) {
- _process_multiplayer(get_peer(p_peer_id), p_peer_id);
- } else {
- emit_signal(SNAME("data_received"), p_peer_id);
- }
-}
-
-void WebSocketServer::_on_connect(int32_t p_peer_id, String p_protocol, String p_resource_name) {
- if (_is_multiplayer) {
- // Send add to clients
- _send_add(p_peer_id);
- emit_signal(SNAME("peer_connected"), p_peer_id);
- } else {
- emit_signal(SNAME("client_connected"), p_peer_id, p_protocol, p_resource_name);
- }
-}
-
-void WebSocketServer::_on_disconnect(int32_t p_peer_id, bool p_was_clean) {
- if (_is_multiplayer) {
- // Send delete to clients
- _send_del(p_peer_id);
- emit_signal(SNAME("peer_disconnected"), p_peer_id);
- } else {
- emit_signal(SNAME("client_disconnected"), p_peer_id, p_was_clean);
- }
-}
-
-void WebSocketServer::_on_close_request(int32_t p_peer_id, int p_code, String p_reason) {
- emit_signal(SNAME("client_close_request"), p_peer_id, p_code, p_reason);
-}
diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h
deleted file mode 100644
index de23ee884d..0000000000
--- a/modules/websocket/websocket_server.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*************************************************************************/
-/* websocket_server.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 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 WEBSOCKET_SERVER_H
-#define WEBSOCKET_SERVER_H
-
-#include "core/crypto/crypto.h"
-#include "core/object/ref_counted.h"
-#include "websocket_multiplayer_peer.h"
-#include "websocket_peer.h"
-
-class WebSocketServer : public WebSocketMultiplayerPeer {
- GDCLASS(WebSocketServer, WebSocketMultiplayerPeer);
- GDCICLASS(WebSocketServer);
-
- IPAddress bind_ip;
-
-protected:
- static void _bind_methods();
-
- Ref<CryptoKey> private_key;
- Ref<X509Certificate> tls_cert;
- Ref<X509Certificate> ca_chain;
- uint32_t handshake_timeout = 3000;
-
-public:
- virtual void set_extra_headers(const Vector<String> &p_headers) = 0;
- virtual Error listen(int p_port, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false) = 0;
- virtual void stop() = 0;
- virtual bool is_listening() const = 0;
- virtual bool has_peer(int p_id) const = 0;
- virtual bool is_server() const override;
- ConnectionStatus get_connection_status() const override;
-
- virtual IPAddress get_peer_address(int p_peer_id) const = 0;
- virtual int get_peer_port(int p_peer_id) const = 0;
- virtual void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = "") = 0;
-
- void _on_peer_packet(int32_t p_peer_id);
- void _on_connect(int32_t p_peer_id, String p_protocol, String p_resource_name);
- void _on_disconnect(int32_t p_peer_id, bool p_was_clean);
- void _on_close_request(int32_t p_peer_id, int p_code, String p_reason);
-
- IPAddress get_bind_ip() const;
- void set_bind_ip(const IPAddress &p_bind_ip);
-
- Ref<CryptoKey> get_private_key() const;
- void set_private_key(Ref<CryptoKey> p_key);
-
- Ref<X509Certificate> get_tls_certificate() const;
- void set_tls_certificate(Ref<X509Certificate> p_cert);
-
- Ref<X509Certificate> get_ca_chain() const;
- void set_ca_chain(Ref<X509Certificate> p_ca_chain);
-
- float get_handshake_timeout() const;
- void set_handshake_timeout(float p_timeout);
-
- WebSocketServer();
- ~WebSocketServer();
-};
-
-#endif // WEBSOCKET_SERVER_H
diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp
deleted file mode 100644
index 50ef53e267..0000000000
--- a/modules/websocket/wsl_client.cpp
+++ /dev/null
@@ -1,407 +0,0 @@
-/*************************************************************************/
-/* wsl_client.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 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 WEB_ENABLED
-
-#include "wsl_client.h"
-#include "core/config/project_settings.h"
-#include "core/io/ip.h"
-
-void WSLClient::_do_handshake() {
- if (_requested < _request.size() - 1) {
- int sent = 0;
- Error err = _connection->put_partial_data(((const uint8_t *)_request.get_data() + _requested), _request.size() - _requested - 1, sent);
- // Sending handshake failed
- if (err != OK) {
- disconnect_from_host();
- _on_error();
- return;
- }
- _requested += sent;
-
- } else {
- int read = 0;
- while (true) {
- if (_resp_pos >= WSL_MAX_HEADER_SIZE) {
- // Header is too big
- disconnect_from_host();
- _on_error();
- ERR_FAIL_MSG("Response headers too big.");
- }
- Error err = _connection->get_partial_data(&_resp_buf[_resp_pos], 1, read);
- if (err == ERR_FILE_EOF) {
- // We got a disconnect.
- disconnect_from_host();
- _on_error();
- return;
- } else if (err != OK) {
- // Got some error.
- disconnect_from_host();
- _on_error();
- return;
- } else if (read != 1) {
- // Busy, wait next poll.
- break;
- }
- // Check "\r\n\r\n" header terminator
- char *r = (char *)_resp_buf;
- int l = _resp_pos;
- if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
- r[l - 3] = '\0';
- String protocol;
- // Response is over, verify headers and create peer.
- if (!_verify_headers(protocol)) {
- disconnect_from_host();
- _on_error();
- ERR_FAIL_MSG("Invalid response headers.");
- }
- // Create peer.
- WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData);
- data->obj = this;
- data->conn = _connection;
- data->tcp = _tcp;
- data->is_server = false;
- data->id = 1;
- _peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size);
- _peer->set_no_delay(true);
- _status = CONNECTION_CONNECTED;
- _on_connect(protocol);
- break;
- }
- _resp_pos += 1;
- }
- }
-}
-
-bool WSLClient::_verify_headers(String &r_protocol) {
- String s = (char *)_resp_buf;
- Vector<String> psa = s.split("\r\n");
- int len = psa.size();
- ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers. Got: " + itos(len) + ", expected >= 4.");
-
- Vector<String> req = psa[0].split(" ", false);
- ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code. Got '" + psa[0] + "', expected 'HTTP/1.1 101'.");
-
- // Wrong protocol
- ERR_FAIL_COND_V_MSG(req[0] != "HTTP/1.1", false, "Invalid protocol. Got: '" + req[0] + "', expected 'HTTP/1.1'.");
- ERR_FAIL_COND_V_MSG(req[1] != "101", false, "Invalid status code. Got: '" + req[1] + "', expected '101'.");
-
- HashMap<String, String> headers;
- for (int i = 1; i < len; i++) {
- Vector<String> header = psa[i].split(":", false, 1);
- ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i] + ".");
- String name = header[0].to_lower();
- String value = header[1].strip_edges();
- if (headers.has(name)) {
- headers[name] += "," + value;
- } else {
- headers[name] = value;
- }
- }
-
-#define WSL_CHECK(NAME, VALUE) \
- ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \
- "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'.");
-#define WSL_CHECK_NC(NAME, VALUE) \
- ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME] != VALUE, false, \
- "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'.");
- WSL_CHECK("connection", "upgrade");
- WSL_CHECK("upgrade", "websocket");
- WSL_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key));
-#undef WSL_CHECK_NC
-#undef WSL_CHECK
- if (_protocols.size() == 0) {
- // We didn't request a custom protocol
- ERR_FAIL_COND_V_MSG(headers.has("sec-websocket-protocol"), false, "Received unrequested sub-protocol -> " + headers["sec-websocket-protocol"]);
- } else {
- // We requested at least one custom protocol but didn't receive one
- ERR_FAIL_COND_V_MSG(!headers.has("sec-websocket-protocol"), false, "Requested sub-protocol(s) but received none.");
- // Check received sub-protocol was one of those requested.
- r_protocol = headers["sec-websocket-protocol"];
- bool valid = false;
- for (int i = 0; i < _protocols.size(); i++) {
- if (_protocols[i] != r_protocol) {
- continue;
- }
- valid = true;
- break;
- }
- if (!valid) {
- ERR_FAIL_V_MSG(false, "Received unrequested sub-protocol -> " + r_protocol);
- return false;
- }
- }
- return true;
-}
-
-Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector<String> p_protocols, const Vector<String> p_custom_headers) {
- ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE);
- ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
-
- _peer = Ref<WSLPeer>(memnew(WSLPeer));
-
- if (p_host.is_valid_ip_address()) {
- _ip_candidates.push_back(IPAddress(p_host));
- } else {
- // Queue hostname for resolution.
- _resolver_id = IP::get_singleton()->resolve_hostname_queue_item(p_host);
- ERR_FAIL_COND_V(_resolver_id == IP::RESOLVER_INVALID_ID, ERR_INVALID_PARAMETER);
- // Check if it was found in cache.
- IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id);
- if (ip_status == IP::RESOLVER_STATUS_DONE) {
- _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id);
- IP::get_singleton()->erase_resolve_item(_resolver_id);
- _resolver_id = IP::RESOLVER_INVALID_ID;
- }
- }
-
- // We assume OK while hostname resolution is pending.
- Error err = _resolver_id != IP::RESOLVER_INVALID_ID ? OK : FAILED;
- while (_ip_candidates.size()) {
- err = _tcp->connect_to_host(_ip_candidates.pop_front(), p_port);
- if (err == OK) {
- break;
- }
- }
- if (err != OK) {
- _tcp->disconnect_from_host();
- _on_error();
- return err;
- }
- _connection = _tcp;
- _use_tls = p_tls;
- _host = p_host;
- _port = p_port;
- // Strip edges from protocols.
- _protocols.resize(p_protocols.size());
- String *pw = _protocols.ptrw();
- for (int i = 0; i < p_protocols.size(); i++) {
- pw[i] = p_protocols[i].strip_edges();
- }
-
- _key = WSLPeer::generate_key();
- String request = "GET " + p_path + " HTTP/1.1\r\n";
- String port = "";
- if ((p_port != 80 && !p_tls) || (p_port != 443 && p_tls)) {
- port = ":" + itos(p_port);
- }
- request += "Host: " + p_host + port + "\r\n";
- request += "Upgrade: websocket\r\n";
- request += "Connection: Upgrade\r\n";
- request += "Sec-WebSocket-Key: " + _key + "\r\n";
- request += "Sec-WebSocket-Version: 13\r\n";
- if (p_protocols.size() > 0) {
- request += "Sec-WebSocket-Protocol: ";
- for (int i = 0; i < p_protocols.size(); i++) {
- if (i != 0) {
- request += ",";
- }
- request += p_protocols[i];
- }
- request += "\r\n";
- }
- for (int i = 0; i < p_custom_headers.size(); i++) {
- request += p_custom_headers[i] + "\r\n";
- }
- request += "\r\n";
- _request = request.utf8();
- _status = CONNECTION_CONNECTING;
-
- return OK;
-}
-
-int WSLClient::get_max_packet_size() const {
- return (1 << _out_buf_size) - PROTO_SIZE;
-}
-
-void WSLClient::poll() {
- if (_resolver_id != IP::RESOLVER_INVALID_ID) {
- IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id);
- if (ip_status == IP::RESOLVER_STATUS_WAITING) {
- return;
- }
- // Anything else is either a candidate or a failure.
- Error err = FAILED;
- if (ip_status == IP::RESOLVER_STATUS_DONE) {
- _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id);
- while (_ip_candidates.size()) {
- err = _tcp->connect_to_host(_ip_candidates.pop_front(), _port);
- if (err == OK) {
- break;
- }
- }
- }
- IP::get_singleton()->erase_resolve_item(_resolver_id);
- _resolver_id = IP::RESOLVER_INVALID_ID;
- if (err != OK) {
- disconnect_from_host();
- _on_error();
- return;
- }
- }
- if (_peer->is_connected_to_host()) {
- _peer->poll();
- if (!_peer->is_connected_to_host()) {
- disconnect_from_host();
- _on_disconnect(_peer->close_code != -1);
- }
- return;
- }
-
- if (_connection.is_null()) {
- return; // Not connected.
- }
-
- _tcp->poll();
- switch (_tcp->get_status()) {
- case StreamPeerTCP::STATUS_NONE:
- // Clean close
- disconnect_from_host();
- _on_error();
- break;
- case StreamPeerTCP::STATUS_CONNECTED: {
- _ip_candidates.clear();
- Ref<StreamPeerTLS> tls;
- if (_use_tls) {
- if (_connection == _tcp) {
- // Start SSL handshake
- tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
- ERR_FAIL_COND_MSG(tls.is_null(), "SSL is not available in this build.");
- tls->set_blocking_handshake_enabled(false);
- if (tls->connect_to_stream(_tcp, verify_tls, _host, tls_cert) != OK) {
- disconnect_from_host();
- _on_error();
- return;
- }
- _connection = tls;
- } else {
- tls = static_cast<Ref<StreamPeerTLS>>(_connection);
- ERR_FAIL_COND(tls.is_null()); // Bug?
- tls->poll();
- }
- if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) {
- return; // Need more polling.
- } else if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) {
- disconnect_from_host();
- _on_error();
- return; // Error.
- }
- }
- // Do websocket handshake.
- _do_handshake();
- } break;
- case StreamPeerTCP::STATUS_ERROR:
- while (_ip_candidates.size() > 0) {
- _tcp->disconnect_from_host();
- if (_tcp->connect_to_host(_ip_candidates.pop_front(), _port) == OK) {
- return;
- }
- }
- disconnect_from_host();
- _on_error();
- break;
- case StreamPeerTCP::STATUS_CONNECTING:
- break; // Wait for connection
- }
-}
-
-Ref<WebSocketPeer> WSLClient::get_peer(int p_peer_id) const {
- ERR_FAIL_COND_V(p_peer_id != 1, nullptr);
-
- return _peer;
-}
-
-MultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const {
- // This is surprising, but keeps the current behaviour to allow clean close requests.
- // TODO Refactor WebSocket and split Client/Server/Multiplayer like done in other peers.
- if (_peer->is_connected_to_host()) {
- return CONNECTION_CONNECTED;
- }
- return _status;
-}
-
-void WSLClient::disconnect_from_host(int p_code, String p_reason) {
- _peer->close(p_code, p_reason);
- _connection = Ref<StreamPeer>(nullptr);
- _tcp = Ref<StreamPeerTCP>(memnew(StreamPeerTCP));
- _status = CONNECTION_DISCONNECTED;
-
- _key = "";
- _host = "";
- _protocols.clear();
- _use_tls = false;
-
- _request = "";
- _requested = 0;
-
- memset(_resp_buf, 0, sizeof(_resp_buf));
- _resp_pos = 0;
-
- if (_resolver_id != IP::RESOLVER_INVALID_ID) {
- IP::get_singleton()->erase_resolve_item(_resolver_id);
- _resolver_id = IP::RESOLVER_INVALID_ID;
- }
-
- _ip_candidates.clear();
-}
-
-IPAddress WSLClient::get_connected_host() const {
- ERR_FAIL_COND_V(!_peer->is_connected_to_host(), IPAddress());
- return _peer->get_connected_host();
-}
-
-uint16_t WSLClient::get_connected_port() const {
- ERR_FAIL_COND_V(!_peer->is_connected_to_host(), 0);
- return _peer->get_connected_port();
-}
-
-Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) {
- ERR_FAIL_COND_V_MSG(_connection.is_valid(), FAILED, "Buffers sizes can only be set before listening or connecting.");
-
- _in_buf_size = nearest_shift(p_in_buffer - 1) + 10;
- _in_pkt_size = nearest_shift(p_in_packets - 1);
- _out_buf_size = nearest_shift(p_out_buffer - 1) + 10;
- _out_pkt_size = nearest_shift(p_out_packets - 1);
- return OK;
-}
-
-WSLClient::WSLClient() {
- _peer.instantiate();
- _tcp.instantiate();
- disconnect_from_host();
-}
-
-WSLClient::~WSLClient() {
- _peer->close_now();
- _peer->invalidate();
- disconnect_from_host();
-}
-
-#endif // WEB_ENABLED
diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h
deleted file mode 100644
index dfb989fdd3..0000000000
--- a/modules/websocket/wsl_client.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*************************************************************************/
-/* wsl_client.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 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 WSL_CLIENT_H
-#define WSL_CLIENT_H
-
-#ifndef WEB_ENABLED
-
-#include "core/error/error_list.h"
-#include "core/io/stream_peer_tcp.h"
-#include "core/io/stream_peer_tls.h"
-#include "websocket_client.h"
-#include "wsl_peer.h"
-#include "wslay/wslay.h"
-
-class WSLClient : public WebSocketClient {
- GDCIIMPL(WSLClient, WebSocketClient);
-
-private:
- int _in_buf_size = DEF_BUF_SHIFT;
- int _in_pkt_size = DEF_PKT_SHIFT;
- int _out_buf_size = DEF_BUF_SHIFT;
- int _out_pkt_size = DEF_PKT_SHIFT;
-
- Ref<WSLPeer> _peer;
- Ref<StreamPeerTCP> _tcp;
- Ref<StreamPeer> _connection;
- ConnectionStatus _status = CONNECTION_DISCONNECTED;
-
- CharString _request;
- int _requested = 0;
-
- uint8_t _resp_buf[WSL_MAX_HEADER_SIZE];
- int _resp_pos = 0;
-
- String _key;
- String _host;
- uint16_t _port = 0;
- Array _ip_candidates;
- Vector<String> _protocols;
- bool _use_tls = false;
- IP::ResolverID _resolver_id = IP::RESOLVER_INVALID_ID;
-
- void _do_handshake();
- bool _verify_headers(String &r_protocol);
-
-public:
- Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) override;
- Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()) override;
- int get_max_packet_size() const override;
- Ref<WebSocketPeer> get_peer(int p_peer_id) const override;
- void disconnect_from_host(int p_code = 1000, String p_reason = "") override;
- IPAddress get_connected_host() const override;
- uint16_t get_connected_port() const override;
- virtual ConnectionStatus get_connection_status() const override;
- virtual void poll() override;
-
- WSLClient();
- ~WSLClient();
-};
-
-#endif // WEB_ENABLED
-
-#endif // WSL_CLIENT_H
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index 97bd87a526..4930b178ec 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -32,71 +32,535 @@
#include "wsl_peer.h"
-#include "wsl_client.h"
-#include "wsl_server.h"
+#include "wsl_peer.h"
-#include "core/crypto/crypto_core.h"
-#include "core/math/random_number_generator.h"
-#include "core/os/os.h"
+#include "core/io/stream_peer_tls.h"
-String WSLPeer::generate_key() {
- // Random key
- RandomNumberGenerator rng;
- rng.set_seed(OS::get_singleton()->get_unix_time());
- Vector<uint8_t> bkey;
- int len = 16; // 16 bytes, as per RFC
- bkey.resize(len);
- uint8_t *w = bkey.ptrw();
- for (int i = 0; i < len; i++) {
- w[i] = (uint8_t)rng.randi_range(0, 255);
+CryptoCore::RandomGenerator *WSLPeer::_static_rng = nullptr;
+
+void WSLPeer::initialize() {
+ WebSocketPeer::_create = WSLPeer::_create;
+ _static_rng = memnew(CryptoCore::RandomGenerator);
+ _static_rng->init();
+}
+
+void WSLPeer::deinitialize() {
+ if (_static_rng) {
+ memdelete(_static_rng);
+ _static_rng = nullptr;
}
- return CryptoCore::b64_encode_str(&w[0], len);
}
-String WSLPeer::compute_key_response(String p_key) {
- String key = p_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Magic UUID as per RFC
- Vector<uint8_t> sha = key.sha1_buffer();
- return CryptoCore::b64_encode_str(sha.ptr(), sha.size());
+///
+/// Resolver
+///
+void WSLPeer::Resolver::start(const String &p_host, int p_port) {
+ stop();
+
+ port = p_port;
+ if (p_host.is_valid_ip_address()) {
+ ip_candidates.push_back(IPAddress(p_host));
+ } else {
+ // Queue hostname for resolution.
+ resolver_id = IP::get_singleton()->resolve_hostname_queue_item(p_host);
+ ERR_FAIL_COND(resolver_id == IP::RESOLVER_INVALID_ID);
+ // Check if it was found in cache.
+ IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(resolver_id);
+ if (ip_status == IP::RESOLVER_STATUS_DONE) {
+ ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolver_id);
+ IP::get_singleton()->erase_resolve_item(resolver_id);
+ resolver_id = IP::RESOLVER_INVALID_ID;
+ }
+ }
}
-void WSLPeer::_wsl_destroy(struct PeerData **p_data) {
- if (!p_data || !(*p_data)) {
- return;
+void WSLPeer::Resolver::stop() {
+ if (resolver_id != IP::RESOLVER_INVALID_ID) {
+ IP::get_singleton()->erase_resolve_item(resolver_id);
+ resolver_id = IP::RESOLVER_INVALID_ID;
+ }
+ port = 0;
+}
+
+void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) {
+ // Check if we still need resolving.
+ if (resolver_id != IP::RESOLVER_INVALID_ID) {
+ IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(resolver_id);
+ if (ip_status == IP::RESOLVER_STATUS_WAITING) {
+ return;
+ }
+ if (ip_status == IP::RESOLVER_STATUS_DONE) {
+ ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolver_id);
+ }
+ IP::get_singleton()->erase_resolve_item(resolver_id);
+ resolver_id = IP::RESOLVER_INVALID_ID;
}
- struct PeerData *data = *p_data;
- if (data->polling) {
- data->destroy = true;
+
+ // Try the current candidate if we have one.
+ if (p_tcp->get_status() != StreamPeerTCP::STATUS_NONE) {
+ p_tcp->poll();
+ StreamPeerTCP::Status status = p_tcp->get_status();
+ if (status == StreamPeerTCP::STATUS_CONNECTED) {
+ p_tcp->set_no_delay(true);
+ ip_candidates.clear();
+ return;
+ } else if (status == StreamPeerTCP::STATUS_CONNECTING) {
+ return; // Keep connecting.
+ } else {
+ p_tcp->disconnect_from_host();
+ }
+ }
+
+ // Keep trying next candidate.
+ while (ip_candidates.size()) {
+ Error err = p_tcp->connect_to_host(ip_candidates.pop_front(), port);
+ if (err == OK) {
+ return;
+ } else {
+ p_tcp->disconnect_from_host();
+ }
+ }
+}
+
+///
+/// Server functions
+///
+Error WSLPeer::accept_stream(Ref<StreamPeer> p_stream) {
+ ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE);
+ ERR_FAIL_COND_V(p_stream.is_null(), ERR_INVALID_PARAMETER);
+
+ _clear();
+
+ if (p_stream->is_class_ptr(StreamPeerTCP::get_class_ptr_static())) {
+ tcp = p_stream;
+ connection = p_stream;
+ use_tls = false;
+ } else if (p_stream->is_class_ptr(StreamPeerTLS::get_class_ptr_static())) {
+ Ref<StreamPeer> base_stream = static_cast<Ref<StreamPeerTLS>>(p_stream)->get_stream();
+ ERR_FAIL_COND_V(base_stream.is_null() || !base_stream->is_class_ptr(StreamPeerTCP::get_class_ptr_static()), ERR_INVALID_PARAMETER);
+ tcp = static_cast<Ref<StreamPeerTCP>>(base_stream);
+ connection = p_stream;
+ use_tls = true;
+ }
+ ERR_FAIL_COND_V(connection.is_null() || tcp.is_null(), ERR_INVALID_PARAMETER);
+ is_server = true;
+ ready_state = STATE_CONNECTING;
+ handshake_buffer->resize(WSL_MAX_HEADER_SIZE);
+ handshake_buffer->seek(0);
+ return OK;
+}
+
+bool WSLPeer::_parse_client_request() {
+ Vector<String> psa = String((const char *)handshake_buffer->get_data_array().ptr(), handshake_buffer->get_position() - 4).split("\r\n");
+ int len = psa.size();
+ ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4.");
+
+ Vector<String> req = psa[0].split(" ", false);
+ ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code.");
+
+ // Wrong protocol
+ ERR_FAIL_COND_V_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", false, "Invalid method or HTTP version.");
+
+ HashMap<String, String> headers;
+ for (int i = 1; i < len; i++) {
+ Vector<String> header = psa[i].split(":", false, 1);
+ ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i]);
+ String name = header[0].to_lower();
+ String value = header[1].strip_edges();
+ if (headers.has(name)) {
+ headers[name] += "," + value;
+ } else {
+ headers[name] = value;
+ }
+ }
+ requested_host = headers.has("host") ? headers.get("host") : "";
+ requested_url = (use_tls ? "wss://" : "ws://") + requested_host + req[1];
+#define WSL_CHECK(NAME, VALUE) \
+ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \
+ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'.");
+#define WSL_CHECK_EX(NAME) \
+ ERR_FAIL_COND_V_MSG(!headers.has(NAME), false, "Missing header '" + String(NAME) + "'.");
+ WSL_CHECK("upgrade", "websocket");
+ WSL_CHECK("sec-websocket-version", "13");
+ WSL_CHECK_EX("sec-websocket-key");
+ WSL_CHECK_EX("connection");
+#undef WSL_CHECK_EX
+#undef WSL_CHECK
+ session_key = headers["sec-websocket-key"];
+ if (headers.has("sec-websocket-protocol")) {
+ Vector<String> protos = headers["sec-websocket-protocol"].split(",");
+ for (int i = 0; i < protos.size(); i++) {
+ String proto = protos[i].strip_edges();
+ // Check if we have the given protocol
+ for (int j = 0; j < supported_protocols.size(); j++) {
+ if (proto != supported_protocols[j]) {
+ continue;
+ }
+ selected_protocol = proto;
+ break;
+ }
+ // Found a protocol
+ if (!selected_protocol.is_empty()) {
+ break;
+ }
+ }
+ if (selected_protocol.is_empty()) { // Invalid protocol(s) requested
+ return false;
+ }
+ } else if (supported_protocols.size() > 0) { // No protocol requested, but we need one
+ return false;
+ }
+ return true;
+}
+
+Error WSLPeer::_do_server_handshake() {
+ if (use_tls) {
+ Ref<StreamPeerTLS> tls = static_cast<Ref<StreamPeerTLS>>(connection);
+ if (tls.is_null()) {
+ ERR_FAIL_V_MSG(ERR_BUG, "Couldn't get StreamPeerTLS for WebSocket handshake.");
+ close(-1);
+ return FAILED;
+ }
+ tls->poll();
+ if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) {
+ return OK; // Pending handshake
+ } else if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) {
+ print_verbose(vformat("WebSocket SSL connection error during handshake (StreamPeerTLS status code %d).", tls->get_status()));
+ close(-1);
+ return FAILED;
+ }
+ }
+
+ if (pending_request) {
+ int read = 0;
+ while (true) {
+ ERR_FAIL_COND_V_MSG(handshake_buffer->get_available_bytes() < 1, ERR_OUT_OF_MEMORY, "WebSocket response headers are too big.");
+ int pos = handshake_buffer->get_position();
+ uint8_t byte;
+ Error err = connection->get_partial_data(&byte, 1, read);
+ if (err != OK) { // Got an error
+ print_verbose(vformat("WebSocket error while getting partial data (StreamPeer error code %d).", err));
+ close(-1);
+ return FAILED;
+ } else if (read != 1) { // Busy, wait next poll
+ return OK;
+ }
+ handshake_buffer->put_u8(byte);
+ const char *r = (const char *)handshake_buffer->get_data_array().ptr();
+ int l = pos;
+ if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
+ if (!_parse_client_request()) {
+ close(-1);
+ return FAILED;
+ }
+ String s = "HTTP/1.1 101 Switching Protocols\r\n";
+ s += "Upgrade: websocket\r\n";
+ s += "Connection: Upgrade\r\n";
+ s += "Sec-WebSocket-Accept: " + _compute_key_response(session_key) + "\r\n";
+ if (!selected_protocol.is_empty()) {
+ s += "Sec-WebSocket-Protocol: " + selected_protocol + "\r\n";
+ }
+ for (int i = 0; i < handshake_headers.size(); i++) {
+ s += handshake_headers[i] + "\r\n";
+ }
+ s += "\r\n";
+ CharString cs = s.utf8();
+ handshake_buffer->clear();
+ handshake_buffer->put_data((const uint8_t *)cs.get_data(), cs.length());
+ handshake_buffer->seek(0);
+ pending_request = false;
+ break;
+ }
+ }
+ }
+
+ if (pending_request) { // Still pending.
+ return OK;
+ }
+
+ int left = handshake_buffer->get_available_bytes();
+ if (left) {
+ Vector<uint8_t> data = handshake_buffer->get_data_array();
+ int pos = handshake_buffer->get_position();
+ int sent = 0;
+ Error err = connection->put_partial_data(data.ptr() + pos, left, sent);
+ if (err != OK) {
+ print_verbose(vformat("WebSocket error while putting partial data (StreamPeer error code %d).", err));
+ close(-1);
+ return err;
+ }
+ handshake_buffer->seek(pos + sent);
+ left -= sent;
+ if (left == 0) {
+ resolver.stop();
+ // Response sent, initialize wslay context.
+ wslay_event_context_server_init(&wsl_ctx, &_wsl_callbacks, this);
+ wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size);
+ in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets);
+ packet_buffer.resize(inbound_buffer_size);
+ ready_state = STATE_OPEN;
+ }
+ }
+
+ return OK;
+}
+
+///
+/// Client functions
+///
+void WSLPeer::_do_client_handshake() {
+ ERR_FAIL_COND(tcp.is_null());
+
+ // Try to connect to candidates.
+ if (resolver.has_more_candidates()) {
+ resolver.try_next_candidate(tcp);
+ if (resolver.has_more_candidates()) {
+ return; // Still pending.
+ }
+ }
+
+ tcp->poll();
+ if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
+ close(-1); // Failed to connect.
return;
}
- wslay_event_context_free(data->ctx);
- memdelete(data);
- *p_data = nullptr;
+
+ if (use_tls) {
+ Ref<StreamPeerTLS> tls;
+ if (connection == tcp) {
+ // Start SSL handshake
+ tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
+ ERR_FAIL_COND_MSG(tls.is_null(), "SSL is not available in this build.");
+ tls->set_blocking_handshake_enabled(false);
+ if (tls->connect_to_stream(tcp, verify_tls, requested_host, tls_cert) != OK) {
+ close(-1);
+ return; // Error.
+ }
+ connection = tls;
+ } else {
+ tls = static_cast<Ref<StreamPeerTLS>>(connection);
+ ERR_FAIL_COND(tls.is_null());
+ tls->poll();
+ }
+ if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) {
+ return; // Need more polling.
+ } else if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) {
+ close(-1);
+ return; // Error.
+ }
+ }
+
+ // Do websocket handshake.
+ if (pending_request) {
+ int left = handshake_buffer->get_available_bytes();
+ int pos = handshake_buffer->get_position();
+ const Vector<uint8_t> data = handshake_buffer->get_data_array();
+ int sent = 0;
+ Error err = connection->put_partial_data(data.ptr() + pos, left, sent);
+ // Sending handshake failed
+ if (err != OK) {
+ close(-1);
+ return; // Error.
+ }
+ handshake_buffer->seek(pos + sent);
+ if (handshake_buffer->get_available_bytes() == 0) {
+ pending_request = false;
+ handshake_buffer->clear();
+ handshake_buffer->resize(WSL_MAX_HEADER_SIZE);
+ handshake_buffer->seek(0);
+ }
+ } else {
+ int read = 0;
+ while (true) {
+ int left = handshake_buffer->get_available_bytes();
+ int pos = handshake_buffer->get_position();
+ if (left == 0) {
+ // Header is too big
+ close(-1);
+ ERR_FAIL_MSG("Response headers too big.");
+ return;
+ }
+
+ uint8_t byte;
+ Error err = connection->get_partial_data(&byte, 1, read);
+ if (err != OK) {
+ // Got some error.
+ close(-1);
+ return;
+ } else if (read != 1) {
+ // Busy, wait next poll.
+ break;
+ }
+ handshake_buffer->put_u8(byte);
+
+ // Check "\r\n\r\n" header terminator
+ const char *r = (const char *)handshake_buffer->get_data_array().ptr();
+ int l = pos;
+ if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
+ // Response is over, verify headers and initialize wslay context/
+ if (!_verify_server_response()) {
+ close(-1);
+ ERR_FAIL_MSG("Invalid response headers.");
+ return;
+ }
+ wslay_event_context_client_init(&wsl_ctx, &_wsl_callbacks, this);
+ wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size);
+ in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets);
+ packet_buffer.resize(inbound_buffer_size);
+ ready_state = STATE_OPEN;
+ break;
+ }
+ }
+ }
}
-bool WSLPeer::_wsl_poll(struct PeerData *p_data) {
- p_data->polling = true;
- int err = 0;
- if ((err = wslay_event_recv(p_data->ctx)) != 0 || (err = wslay_event_send(p_data->ctx)) != 0) {
- print_verbose("Websocket (wslay) poll error: " + itos(err));
- p_data->destroy = true;
+bool WSLPeer::_verify_server_response() {
+ Vector<String> psa = String((const char *)handshake_buffer->get_data_array().ptr(), handshake_buffer->get_position() - 4).split("\r\n");
+ int len = psa.size();
+ ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers. Got: " + itos(len) + ", expected >= 4.");
+
+ Vector<String> req = psa[0].split(" ", false);
+ ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code. Got '" + psa[0] + "', expected 'HTTP/1.1 101'.");
+
+ // Wrong protocol
+ ERR_FAIL_COND_V_MSG(req[0] != "HTTP/1.1", false, "Invalid protocol. Got: '" + req[0] + "', expected 'HTTP/1.1'.");
+ ERR_FAIL_COND_V_MSG(req[1] != "101", false, "Invalid status code. Got: '" + req[1] + "', expected '101'.");
+
+ HashMap<String, String> headers;
+ for (int i = 1; i < len; i++) {
+ Vector<String> header = psa[i].split(":", false, 1);
+ ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i] + ".");
+ String name = header[0].to_lower();
+ String value = header[1].strip_edges();
+ if (headers.has(name)) {
+ headers[name] += "," + value;
+ } else {
+ headers[name] = value;
+ }
}
- p_data->polling = false;
- if (p_data->destroy || (wslay_event_get_close_sent(p_data->ctx) && wslay_event_get_close_received(p_data->ctx))) {
- bool valid = p_data->valid;
- _wsl_destroy(&p_data);
- return valid;
+#define WSL_CHECK(NAME, VALUE) \
+ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \
+ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'.");
+#define WSL_CHECK_NC(NAME, VALUE) \
+ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME] != VALUE, false, \
+ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'.");
+ WSL_CHECK("connection", "upgrade");
+ WSL_CHECK("upgrade", "websocket");
+ WSL_CHECK_NC("sec-websocket-accept", _compute_key_response(session_key));
+#undef WSL_CHECK_NC
+#undef WSL_CHECK
+ if (supported_protocols.size() == 0) {
+ // We didn't request a custom protocol
+ ERR_FAIL_COND_V_MSG(headers.has("sec-websocket-protocol"), false, "Received unrequested sub-protocol -> " + headers["sec-websocket-protocol"]);
+ } else {
+ // We requested at least one custom protocol but didn't receive one
+ ERR_FAIL_COND_V_MSG(!headers.has("sec-websocket-protocol"), false, "Requested sub-protocol(s) but received none.");
+ // Check received sub-protocol was one of those requested.
+ selected_protocol = headers["sec-websocket-protocol"];
+ bool valid = false;
+ for (int i = 0; i < supported_protocols.size(); i++) {
+ if (supported_protocols[i] != selected_protocol) {
+ continue;
+ }
+ valid = true;
+ break;
+ }
+ if (!valid) {
+ ERR_FAIL_V_MSG(false, "Received unrequested sub-protocol -> " + selected_protocol);
+ return false;
+ }
+ }
+ return true;
+}
+
+Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_cert) {
+ ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE);
+ ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER);
+
+ _clear();
+
+ String host;
+ String path;
+ String scheme;
+ int port = 0;
+ Error err = p_url.parse_url(scheme, host, port, path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);
+ if (scheme.is_empty()) {
+ scheme = "ws://";
+ }
+ ERR_FAIL_COND_V_MSG(scheme != "ws://" && scheme != "wss://", ERR_INVALID_PARAMETER, vformat("Invalid protocol: \"%s\" (must be either \"ws://\" or \"wss://\").", scheme));
+
+ use_tls = false;
+ if (scheme == "wss://") {
+ use_tls = true;
+ }
+ if (port == 0) {
+ port = use_tls ? 443 : 80;
+ }
+ if (path.is_empty()) {
+ path = "/";
+ }
+
+ requested_url = p_url;
+ requested_host = host;
+ verify_tls = p_verify_tls;
+ tls_cert = p_cert;
+ tcp.instantiate();
+
+ resolver.start(host, port);
+ resolver.try_next_candidate(tcp);
+
+ if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTING && !resolver.has_more_candidates()) {
+ _clear();
+ return FAILED;
+ }
+ connection = tcp;
+
+ // Prepare handshake request.
+ session_key = _generate_key();
+ String request = "GET " + path + " HTTP/1.1\r\n";
+ String port_string;
+ if ((port != 80 && !use_tls) || (port != 443 && use_tls)) {
+ port_string = ":" + itos(port);
+ }
+ request += "Host: " + host + port_string + "\r\n";
+ request += "Upgrade: websocket\r\n";
+ request += "Connection: Upgrade\r\n";
+ request += "Sec-WebSocket-Key: " + session_key + "\r\n";
+ request += "Sec-WebSocket-Version: 13\r\n";
+ if (supported_protocols.size() > 0) {
+ request += "Sec-WebSocket-Protocol: ";
+ for (int i = 0; i < supported_protocols.size(); i++) {
+ if (i != 0) {
+ request += ",";
+ }
+ request += supported_protocols[i];
+ }
+ request += "\r\n";
}
- return false;
+ for (int i = 0; i < handshake_headers.size(); i++) {
+ request += handshake_headers[i] + "\r\n";
+ }
+ request += "\r\n";
+ CharString cs = request.utf8();
+ handshake_buffer->put_data((const uint8_t *)cs.get_data(), cs.length());
+ handshake_buffer->seek(0);
+ ready_state = STATE_CONNECTING;
+ is_server = false;
+ return OK;
}
-ssize_t wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data) {
- struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data;
- if (!peer_data->valid) {
+///
+/// Callback functions.
+///
+ssize_t WSLPeer::_wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data) {
+ WSLPeer *peer = (WSLPeer *)user_data;
+ Ref<StreamPeer> conn = peer->connection;
+ if (conn.is_null()) {
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
return -1;
}
- Ref<StreamPeer> conn = peer_data->conn;
int read = 0;
Error err = conn->get_partial_data(data, len, read);
if (err != OK) {
@@ -111,13 +575,13 @@ ssize_t wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len
return read;
}
-ssize_t wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) {
- struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data;
- if (!peer_data->valid) {
+ssize_t WSLPeer::_wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) {
+ WSLPeer *peer = (WSLPeer *)user_data;
+ Ref<StreamPeer> conn = peer->connection;
+ if (conn.is_null()) {
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
return -1;
}
- Ref<StreamPeer> conn = peer_data->conn;
int sent = 0;
Error err = conn->put_partial_data(data, len, sent);
if (err != OK) {
@@ -131,144 +595,142 @@ ssize_t wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size
return sent;
}
-int wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) {
- RandomNumberGenerator rng;
- // TODO maybe use crypto in the future?
- rng.set_seed(OS::get_singleton()->get_unix_time());
- for (unsigned int i = 0; i < len; i++) {
- buf[i] = (uint8_t)rng.randi_range(0, 255);
- }
+int WSLPeer::_wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) {
+ ERR_FAIL_COND_V(!_static_rng, WSLAY_ERR_CALLBACK_FAILURE);
+ Error err = _static_rng->get_random_bytes(buf, len);
+ ERR_FAIL_COND_V(err != OK, WSLAY_ERR_CALLBACK_FAILURE);
return 0;
}
-void wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) {
- struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data;
- if (!peer_data->valid || peer_data->closing) {
+void WSLPeer::_wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) {
+ WSLPeer *peer = (WSLPeer *)user_data;
+ uint8_t op = arg->opcode;
+
+ if (op == WSLAY_CONNECTION_CLOSE) {
+ // Close request or confirmation.
+ peer->close_code = arg->status_code;
+ size_t len = arg->msg_length;
+ peer->close_reason = "";
+ if (len > 2 /* first 2 bytes = close code */) {
+ peer->close_reason.parse_utf8((char *)arg->msg + 2, len - 2);
+ }
+ if (peer->ready_state == STATE_OPEN) {
+ peer->ready_state = STATE_CLOSING;
+ }
return;
}
- WSLPeer *peer = static_cast<WSLPeer *>(peer_data->peer);
- if (peer->parse_message(arg) != OK) {
+ if (peer->ready_state == STATE_CLOSING) {
return;
}
- if (peer_data->is_server) {
- WSLServer *helper = static_cast<WSLServer *>(peer_data->obj);
- helper->_on_peer_packet(peer_data->id);
- } else {
- WSLClient *helper = static_cast<WSLClient *>(peer_data->obj);
- helper->_on_peer_packet();
+ if (op == WSLAY_TEXT_FRAME || op == WSLAY_BINARY_FRAME) {
+ // Message.
+ uint8_t is_string = arg->opcode == WSLAY_TEXT_FRAME ? 1 : 0;
+ peer->in_buffer.write_packet(arg->msg, arg->msg_length, &is_string);
}
+ // Ping or pong.
}
-wslay_event_callbacks wsl_callbacks = {
- wsl_recv_callback,
- wsl_send_callback,
- wsl_genmask_callback,
+wslay_event_callbacks WSLPeer::_wsl_callbacks = {
+ _wsl_recv_callback,
+ _wsl_send_callback,
+ _wsl_genmask_callback,
nullptr, /* on_frame_recv_start_callback */
nullptr, /* on_frame_recv_callback */
nullptr, /* on_frame_recv_end_callback */
- wsl_msg_recv_callback
+ _wsl_msg_recv_callback
};
-Error WSLPeer::parse_message(const wslay_event_on_msg_recv_arg *arg) {
- uint8_t is_string = 0;
- if (arg->opcode == WSLAY_TEXT_FRAME) {
- is_string = 1;
- } else if (arg->opcode == WSLAY_CONNECTION_CLOSE) {
- close_code = arg->status_code;
- size_t len = arg->msg_length;
- close_reason = "";
- if (len > 2 /* first 2 bytes = close code */) {
- close_reason.parse_utf8((char *)arg->msg + 2, len - 2);
- }
- if (!wslay_event_get_close_sent(_data->ctx)) {
- if (_data->is_server) {
- WSLServer *helper = static_cast<WSLServer *>(_data->obj);
- helper->_on_close_request(_data->id, close_code, close_reason);
- } else {
- WSLClient *helper = static_cast<WSLClient *>(_data->obj);
- helper->_on_close_request(close_code, close_reason);
- }
- }
- return ERR_FILE_EOF;
- } else if (arg->opcode != WSLAY_BINARY_FRAME) {
- // Ping or pong
- return ERR_SKIP;
- }
- _in_buffer.write_packet(arg->msg, arg->msg_length, &is_string);
- return OK;
-}
-
-void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) {
- ERR_FAIL_COND(_data != nullptr);
- ERR_FAIL_COND(p_data == nullptr);
-
- _in_buffer.resize(p_in_pkt_size, p_in_buf_size);
- _packet_buffer.resize(1 << p_in_buf_size);
- _out_buf_size = p_out_buf_size;
- _out_pkt_size = p_out_pkt_size;
-
- _data = p_data;
- _data->peer = this;
- _data->valid = true;
-
- if (_data->is_server) {
- wslay_event_context_server_init(&(_data->ctx), &wsl_callbacks, _data);
- } else {
- wslay_event_context_client_init(&(_data->ctx), &wsl_callbacks, _data);
- }
- wslay_event_config_set_max_recv_msg_length(_data->ctx, (1ULL << p_in_buf_size));
-}
-
-void WSLPeer::set_write_mode(WriteMode p_mode) {
- write_mode = p_mode;
+String WSLPeer::_generate_key() {
+ // Random key
+ Vector<uint8_t> bkey;
+ int len = 16; // 16 bytes, as per RFC
+ bkey.resize(len);
+ _wsl_genmask_callback(nullptr, bkey.ptrw(), len, nullptr);
+ return CryptoCore::b64_encode_str(bkey.ptrw(), len);
}
-WSLPeer::WriteMode WSLPeer::get_write_mode() const {
- return write_mode;
+String WSLPeer::_compute_key_response(String p_key) {
+ String key = p_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Magic UUID as per RFC
+ Vector<uint8_t> sha = key.sha1_buffer();
+ return CryptoCore::b64_encode_str(sha.ptr(), sha.size());
}
void WSLPeer::poll() {
- if (!_data) {
+ // Nothing to do.
+ if (ready_state == STATE_CLOSED) {
return;
}
- if (_wsl_poll(_data)) {
- _data = nullptr;
+ if (ready_state == STATE_CONNECTING) {
+ if (is_server) {
+ _do_server_handshake();
+ } else {
+ _do_client_handshake();
+ }
+ }
+
+ if (ready_state == STATE_OPEN || ready_state == STATE_CLOSING) {
+ ERR_FAIL_COND(!wsl_ctx);
+ int err = 0;
+ if ((err = wslay_event_recv(wsl_ctx)) != 0 || (err = wslay_event_send(wsl_ctx)) != 0) {
+ // Error close.
+ print_verbose("Websocket (wslay) poll error: " + itos(err));
+ wslay_event_context_free(wsl_ctx);
+ wsl_ctx = nullptr;
+ close(-1);
+ return;
+ }
+ if (wslay_event_get_close_sent(wsl_ctx) && wslay_event_get_close_received(wsl_ctx)) {
+ // Clean close.
+ wslay_event_context_free(wsl_ctx);
+ wsl_ctx = nullptr;
+ close(-1);
+ return;
+ }
}
}
-Error WSLPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
- ERR_FAIL_COND_V(!is_connected_to_host(), FAILED);
- ERR_FAIL_COND_V(_out_pkt_size && (wslay_event_get_queued_msg_count(_data->ctx) >= (1ULL << _out_pkt_size)), ERR_OUT_OF_MEMORY);
- ERR_FAIL_COND_V(_out_buf_size && (wslay_event_get_queued_msg_length(_data->ctx) + p_buffer_size >= (1ULL << _out_buf_size)), ERR_OUT_OF_MEMORY);
+Error WSLPeer::_send(const uint8_t *p_buffer, int p_buffer_size, wslay_opcode p_opcode) {
+ ERR_FAIL_COND_V(ready_state != STATE_OPEN, FAILED);
+ ERR_FAIL_COND_V(wslay_event_get_queued_msg_count(wsl_ctx) >= (uint32_t)max_queued_packets, ERR_OUT_OF_MEMORY);
+ ERR_FAIL_COND_V(outbound_buffer_size > 0 && (wslay_event_get_queued_msg_length(wsl_ctx) + p_buffer_size > (uint32_t)outbound_buffer_size), ERR_OUT_OF_MEMORY);
struct wslay_event_msg msg;
- msg.opcode = write_mode == WRITE_MODE_TEXT ? WSLAY_TEXT_FRAME : WSLAY_BINARY_FRAME;
+ msg.opcode = p_opcode;
msg.msg = p_buffer;
msg.msg_length = p_buffer_size;
// Queue & send message.
- if (wslay_event_queue_msg(_data->ctx, &msg) != 0 || wslay_event_send(_data->ctx) != 0) {
- close_now();
+ if (wslay_event_queue_msg(wsl_ctx, &msg) != 0 || wslay_event_send(wsl_ctx) != 0) {
+ close(-1);
return FAILED;
}
return OK;
}
+Error WSLPeer::send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) {
+ wslay_opcode opcode = p_mode == WRITE_MODE_TEXT ? WSLAY_TEXT_FRAME : WSLAY_BINARY_FRAME;
+ return _send(p_buffer, p_buffer_size, opcode);
+}
+
+Error WSLPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
+ return _send(p_buffer, p_buffer_size, WSLAY_BINARY_FRAME);
+}
+
Error WSLPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
r_buffer_size = 0;
- ERR_FAIL_COND_V(!is_connected_to_host(), FAILED);
+ ERR_FAIL_COND_V(ready_state != STATE_OPEN, FAILED);
- if (_in_buffer.packets_left() == 0) {
+ if (in_buffer.packets_left() == 0) {
return ERR_UNAVAILABLE;
}
int read = 0;
- uint8_t *rw = _packet_buffer.ptrw();
- _in_buffer.read_packet(rw, _packet_buffer.size(), &_is_string, read);
+ uint8_t *rw = packet_buffer.ptrw();
+ in_buffer.read_packet(rw, packet_buffer.size(), &was_string, read);
*r_buffer = rw;
r_buffer_size = read;
@@ -277,75 +739,106 @@ Error WSLPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
}
int WSLPeer::get_available_packet_count() const {
- if (!is_connected_to_host()) {
+ if (ready_state != STATE_OPEN) {
return 0;
}
- return _in_buffer.packets_left();
+ return in_buffer.packets_left();
}
int WSLPeer::get_current_outbound_buffered_amount() const {
- ERR_FAIL_COND_V(!_data, 0);
-
- return wslay_event_get_queued_msg_length(_data->ctx);
-}
-
-bool WSLPeer::was_string_packet() const {
- return _is_string;
-}
-
-bool WSLPeer::is_connected_to_host() const {
- return _data != nullptr;
-}
+ if (ready_state != STATE_OPEN) {
+ return 0;
+ }
-void WSLPeer::close_now() {
- close(1000, "");
- _wsl_destroy(&_data);
+ return wslay_event_get_queued_msg_length(wsl_ctx);
}
void WSLPeer::close(int p_code, String p_reason) {
- if (_data && !wslay_event_get_close_sent(_data->ctx)) {
+ if (p_code < 0) {
+ // Force immediate close.
+ ready_state = STATE_CLOSED;
+ }
+
+ if (ready_state == STATE_OPEN && !wslay_event_get_close_sent(wsl_ctx)) {
CharString cs = p_reason.utf8();
- wslay_event_queue_close(_data->ctx, p_code, (uint8_t *)cs.ptr(), cs.size());
- wslay_event_send(_data->ctx);
- _data->closing = true;
+ wslay_event_queue_close(wsl_ctx, p_code, (uint8_t *)cs.ptr(), cs.length());
+ wslay_event_send(wsl_ctx);
+ ready_state = STATE_CLOSING;
+ } else if (ready_state == STATE_CONNECTING || ready_state == STATE_CLOSED) {
+ ready_state = STATE_CLOSED;
+ connection.unref();
+ if (tcp.is_valid()) {
+ tcp->disconnect_from_host();
+ tcp.unref();
+ }
}
- _in_buffer.clear();
- _packet_buffer.resize(0);
+ in_buffer.clear();
+ packet_buffer.resize(0);
}
IPAddress WSLPeer::get_connected_host() const {
- ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), IPAddress());
-
- return _data->tcp->get_connected_host();
+ ERR_FAIL_COND_V(tcp.is_null(), IPAddress());
+ return tcp->get_connected_host();
}
uint16_t WSLPeer::get_connected_port() const {
- ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), 0);
+ ERR_FAIL_COND_V(tcp.is_null(), 0);
+ return tcp->get_connected_port();
+}
+
+String WSLPeer::get_selected_protocol() const {
+ return selected_protocol;
+}
- return _data->tcp->get_connected_port();
+String WSLPeer::get_requested_url() const {
+ return requested_url;
}
void WSLPeer::set_no_delay(bool p_enabled) {
- ERR_FAIL_COND(!is_connected_to_host() || _data->tcp.is_null());
- _data->tcp->set_no_delay(p_enabled);
+ ERR_FAIL_COND(tcp.is_null());
+ tcp->set_no_delay(p_enabled);
}
-void WSLPeer::invalidate() {
- if (_data) {
- _data->valid = false;
+void WSLPeer::_clear() {
+ // Connection info.
+ ready_state = STATE_CLOSED;
+ is_server = false;
+ connection.unref();
+ if (tcp.is_valid()) {
+ tcp->disconnect_from_host();
+ tcp.unref();
}
+ if (wsl_ctx) {
+ wslay_event_context_free(wsl_ctx);
+ wsl_ctx = nullptr;
+ }
+
+ resolver.stop();
+ requested_url.clear();
+ requested_host.clear();
+ pending_request = true;
+ handshake_buffer->clear();
+ selected_protocol.clear();
+ session_key.clear();
+
+ // Pending packets info.
+ was_string = 0;
+ in_buffer.clear();
+ packet_buffer.clear();
+
+ // Close code info.
+ close_code = -1;
+ close_reason.clear();
}
WSLPeer::WSLPeer() {
+ handshake_buffer.instantiate();
}
WSLPeer::~WSLPeer() {
- close();
- invalidate();
- _wsl_destroy(&_data);
- _data = nullptr;
+ close(-1);
}
#endif // WEB_ENABLED
diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h
index 92672eb2c4..379002739c 100644
--- a/modules/websocket/wsl_peer.h
+++ b/modules/websocket/wsl_peer.h
@@ -33,79 +33,123 @@
#ifndef WEB_ENABLED
+#include "websocket_peer.h"
+
+#include "packet_buffer.h"
+
+#include "core/crypto/crypto_core.h"
#include "core/error/error_list.h"
#include "core/io/packet_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/templates/ring_buffer.h"
-#include "packet_buffer.h"
-#include "websocket_peer.h"
#include "wslay/wslay.h"
#define WSL_MAX_HEADER_SIZE 4096
class WSLPeer : public WebSocketPeer {
- GDCIIMPL(WSLPeer, WebSocketPeer);
-
-public:
- struct PeerData {
- bool polling = false;
- bool destroy = false;
- bool valid = false;
- bool is_server = false;
- bool closing = false;
- void *obj = nullptr;
- void *peer = nullptr;
- Ref<StreamPeer> conn;
- Ref<StreamPeerTCP> tcp;
- int id = 1;
- wslay_event_context_ptr ctx = nullptr;
+private:
+ static CryptoCore::RandomGenerator *_static_rng;
+ static WebSocketPeer *_create() { return memnew(WSLPeer); }
+
+ // Callbacks.
+ static ssize_t _wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data);
+ static ssize_t _wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data);
+ static int _wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data);
+ static void _wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data);
+
+ static wslay_event_callbacks _wsl_callbacks;
+
+ // Helpers
+ static String _compute_key_response(String p_key);
+ static String _generate_key();
+
+ // Client IP resolver.
+ class Resolver {
+ Array ip_candidates;
+ IP::ResolverID resolver_id = IP::RESOLVER_INVALID_ID;
+ int port = 0;
+
+ public:
+ bool has_more_candidates() {
+ return ip_candidates.size() > 0 || resolver_id != IP::RESOLVER_INVALID_ID;
+ }
+
+ void try_next_candidate(Ref<StreamPeerTCP> &p_tcp);
+ void start(const String &p_host, int p_port);
+ void stop();
+ Resolver() {}
};
- static String compute_key_response(String p_key);
- static String generate_key();
+ Resolver resolver;
-private:
- static bool _wsl_poll(struct PeerData *p_data);
- static void _wsl_destroy(struct PeerData **p_data);
+ // WebSocket connection state.
+ WebSocketPeer::State ready_state = WebSocketPeer::STATE_CLOSED;
+ bool is_server = false;
+ Ref<StreamPeerTCP> tcp;
+ Ref<StreamPeer> connection;
+ wslay_event_context_ptr wsl_ctx = nullptr;
+
+ String requested_url;
+ String requested_host;
+ bool pending_request = true;
+ Ref<StreamPeerBuffer> handshake_buffer;
+ String selected_protocol;
+ String session_key;
- struct PeerData *_data = nullptr;
- uint8_t _is_string = 0;
+ int close_code = -1;
+ String close_reason;
+ uint8_t was_string = 0;
+
+ // WebSocket configuration.
+ bool use_tls = true;
+ bool verify_tls = true;
+ Ref<X509Certificate> tls_cert;
+
+ // Packet buffers.
+ Vector<uint8_t> packet_buffer;
// Our packet info is just a boolean (is_string), using uint8_t for it.
- PacketBuffer<uint8_t> _in_buffer;
+ PacketBuffer<uint8_t> in_buffer;
- Vector<uint8_t> _packet_buffer;
+ Error _send(const uint8_t *p_buffer, int p_buffer_size, wslay_opcode p_opcode);
- WriteMode write_mode = WRITE_MODE_BINARY;
+ Error _do_server_handshake();
+ bool _parse_client_request();
- int _out_buf_size = 0;
- int _out_pkt_size = 0;
+ void _do_client_handshake();
+ bool _verify_server_response();
+
+ void _clear();
public:
- int close_code = -1;
- String close_reason;
- void poll(); // Used by client and server.
+ static void initialize();
+ static void deinitialize();
+ // PacketPeer
virtual int get_available_packet_count() const override;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
- virtual int get_max_packet_size() const override { return _packet_buffer.size(); };
- virtual int get_current_outbound_buffered_amount() const override;
+ virtual int get_max_packet_size() const override { return packet_buffer.size(); };
- virtual void close_now();
+ // WebSocketPeer
+ virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override;
+ virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) override;
+ virtual Error accept_stream(Ref<StreamPeer> p_stream) override;
virtual void close(int p_code = 1000, String p_reason = "") override;
- virtual bool is_connected_to_host() const override;
+ virtual void poll() override;
+
+ virtual State get_ready_state() const override { return ready_state; }
+ virtual int get_close_code() const override { return close_code; }
+ virtual String get_close_reason() const override { return close_reason; }
+ virtual int get_current_outbound_buffered_amount() const override;
+
virtual IPAddress get_connected_host() const override;
virtual uint16_t get_connected_port() const override;
+ virtual String get_selected_protocol() const override;
+ virtual String get_requested_url() const override;
- virtual WriteMode get_write_mode() const override;
- virtual void set_write_mode(WriteMode p_mode) override;
- virtual bool was_string_packet() const override;
+ virtual bool was_string_packet() const override { return was_string; }
virtual void set_no_delay(bool p_enabled) override;
- void make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size);
- Error parse_message(const wslay_event_on_msg_recv_arg *arg);
- void invalidate();
-
WSLPeer();
~WSLPeer();
};
diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp
deleted file mode 100644
index 01dcd53839..0000000000
--- a/modules/websocket/wsl_server.cpp
+++ /dev/null
@@ -1,329 +0,0 @@
-/*************************************************************************/
-/* wsl_server.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 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 WEB_ENABLED
-
-#include "wsl_server.h"
-#include "core/config/project_settings.h"
-#include "core/os/os.h"
-
-bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols, String &r_resource_name) {
- Vector<String> psa = String((char *)req_buf).split("\r\n");
- int len = psa.size();
- ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4.");
-
- Vector<String> req = psa[0].split(" ", false);
- ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code.");
-
- // Wrong protocol
- ERR_FAIL_COND_V_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", false, "Invalid method or HTTP version.");
-
- r_resource_name = req[1];
- HashMap<String, String> headers;
- for (int i = 1; i < len; i++) {
- Vector<String> header = psa[i].split(":", false, 1);
- ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i]);
- String name = header[0].to_lower();
- String value = header[1].strip_edges();
- if (headers.has(name)) {
- headers[name] += "," + value;
- } else {
- headers[name] = value;
- }
- }
-#define WSL_CHECK(NAME, VALUE) \
- ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \
- "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'.");
-#define WSL_CHECK_EX(NAME) \
- ERR_FAIL_COND_V_MSG(!headers.has(NAME), false, "Missing header '" + String(NAME) + "'.");
- WSL_CHECK("upgrade", "websocket");
- WSL_CHECK("sec-websocket-version", "13");
- WSL_CHECK_EX("sec-websocket-key");
- WSL_CHECK_EX("connection");
-#undef WSL_CHECK_EX
-#undef WSL_CHECK
- key = headers["sec-websocket-key"];
- if (headers.has("sec-websocket-protocol")) {
- Vector<String> protos = headers["sec-websocket-protocol"].split(",");
- for (int i = 0; i < protos.size(); i++) {
- String proto = protos[i].strip_edges();
- // Check if we have the given protocol
- for (int j = 0; j < p_protocols.size(); j++) {
- if (proto != p_protocols[j]) {
- continue;
- }
- protocol = proto;
- break;
- }
- // Found a protocol
- if (!protocol.is_empty()) {
- break;
- }
- }
- if (protocol.is_empty()) { // Invalid protocol(s) requested
- return false;
- }
- } else if (p_protocols.size() > 0) { // No protocol requested, but we need one
- return false;
- }
- return true;
-}
-
-Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols, uint64_t p_timeout, String &r_resource_name, const Vector<String> &p_extra_headers) {
- if (OS::get_singleton()->get_ticks_msec() - time > p_timeout) {
- print_verbose(vformat("WebSocket handshake timed out after %.3f seconds.", p_timeout * 0.001));
- return ERR_TIMEOUT;
- }
-
- if (use_tls) {
- Ref<StreamPeerTLS> tls = static_cast<Ref<StreamPeerTLS>>(connection);
- if (tls.is_null()) {
- ERR_FAIL_V_MSG(ERR_BUG, "Couldn't get StreamPeerTLS for WebSocket handshake.");
- }
- tls->poll();
- if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) {
- return ERR_BUSY;
- } else if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) {
- print_verbose(vformat("WebSocket SSL connection error during handshake (StreamPeerTLS status code %d).", tls->get_status()));
- return FAILED;
- }
- }
-
- if (!has_request) {
- int read = 0;
- while (true) {
- ERR_FAIL_COND_V_MSG(req_pos >= WSL_MAX_HEADER_SIZE, ERR_OUT_OF_MEMORY, "WebSocket response headers are too big.");
- Error err = connection->get_partial_data(&req_buf[req_pos], 1, read);
- if (err != OK) { // Got an error
- print_verbose(vformat("WebSocket error while getting partial data (StreamPeer error code %d).", err));
- return FAILED;
- } else if (read != 1) { // Busy, wait next poll
- return ERR_BUSY;
- }
- char *r = (char *)req_buf;
- int l = req_pos;
- if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
- r[l - 3] = '\0';
- if (!_parse_request(p_protocols, r_resource_name)) {
- return FAILED;
- }
- String s = "HTTP/1.1 101 Switching Protocols\r\n";
- s += "Upgrade: websocket\r\n";
- s += "Connection: Upgrade\r\n";
- s += "Sec-WebSocket-Accept: " + WSLPeer::compute_key_response(key) + "\r\n";
- if (!protocol.is_empty()) {
- s += "Sec-WebSocket-Protocol: " + protocol + "\r\n";
- }
- for (int i = 0; i < p_extra_headers.size(); i++) {
- s += p_extra_headers[i] + "\r\n";
- }
- s += "\r\n";
- response = s.utf8();
- has_request = true;
- break;
- }
- req_pos += 1;
- }
- }
-
- if (has_request && response_sent < response.size() - 1) {
- int sent = 0;
- Error err = connection->put_partial_data((const uint8_t *)response.get_data() + response_sent, response.size() - response_sent - 1, sent);
- if (err != OK) {
- print_verbose(vformat("WebSocket error while putting partial data (StreamPeer error code %d).", err));
- return err;
- }
- response_sent += sent;
- }
-
- if (response_sent < response.size() - 1) {
- return ERR_BUSY;
- }
-
- return OK;
-}
-
-void WSLServer::set_extra_headers(const Vector<String> &p_headers) {
- _extra_headers = p_headers;
-}
-
-Error WSLServer::listen(int p_port, const Vector<String> p_protocols, bool gd_mp_api) {
- ERR_FAIL_COND_V(is_listening(), ERR_ALREADY_IN_USE);
-
- _is_multiplayer = gd_mp_api;
- // Strip edges from protocols.
- _protocols.resize(p_protocols.size());
- String *pw = _protocols.ptrw();
- for (int i = 0; i < p_protocols.size(); i++) {
- pw[i] = p_protocols[i].strip_edges();
- }
- return _server->listen(p_port, bind_ip);
-}
-
-void WSLServer::poll() {
- List<int> remove_ids;
- for (const KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) {
- Ref<WSLPeer> peer = const_cast<WSLPeer *>(static_cast<const WSLPeer *>(E.value.ptr()));
- peer->poll();
- if (!peer->is_connected_to_host()) {
- _on_disconnect(E.key, peer->close_code != -1);
- remove_ids.push_back(E.key);
- }
- }
- for (int &E : remove_ids) {
- _peer_map.erase(E);
- }
- remove_ids.clear();
-
- List<Ref<PendingPeer>> remove_peers;
- for (const Ref<PendingPeer> &E : _pending) {
- String resource_name;
- Ref<PendingPeer> ppeer = E;
- Error err = ppeer->do_handshake(_protocols, handshake_timeout, resource_name, _extra_headers);
- if (err == ERR_BUSY) {
- continue;
- } else if (err != OK) {
- remove_peers.push_back(ppeer);
- continue;
- }
- // Creating new peer
- int32_t id = generate_unique_id();
-
- WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData);
- data->obj = this;
- data->conn = ppeer->connection;
- data->tcp = ppeer->tcp;
- data->is_server = true;
- data->id = id;
-
- Ref<WSLPeer> ws_peer = memnew(WSLPeer);
- ws_peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size);
- ws_peer->set_no_delay(true);
-
- _peer_map[id] = ws_peer;
- remove_peers.push_back(ppeer);
- _on_connect(id, ppeer->protocol, resource_name);
- }
- for (const Ref<PendingPeer> &E : remove_peers) {
- _pending.erase(E);
- }
- remove_peers.clear();
-
- if (!_server->is_listening()) {
- return;
- }
-
- while (_server->is_connection_available()) {
- Ref<StreamPeerTCP> conn = _server->take_connection();
- if (is_refusing_new_connections()) {
- continue; // Conn will go out-of-scope and be closed.
- }
-
- Ref<PendingPeer> peer = memnew(PendingPeer);
- if (private_key.is_valid() && tls_cert.is_valid()) {
- Ref<StreamPeerTLS> tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
- tls->set_blocking_handshake_enabled(false);
- tls->accept_stream(conn, private_key, tls_cert, ca_chain);
- peer->connection = tls;
- peer->use_tls = true;
- } else {
- peer->connection = conn;
- }
- peer->tcp = conn;
- peer->time = OS::get_singleton()->get_ticks_msec();
- _pending.push_back(peer);
- }
-}
-
-bool WSLServer::is_listening() const {
- return _server->is_listening();
-}
-
-int WSLServer::get_max_packet_size() const {
- return (1 << _out_buf_size) - PROTO_SIZE;
-}
-
-void WSLServer::stop() {
- _server->stop();
- for (const KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) {
- Ref<WSLPeer> peer = const_cast<WSLPeer *>(static_cast<const WSLPeer *>(E.value.ptr()));
- peer->close_now();
- }
- _pending.clear();
- _peer_map.clear();
- _protocols.clear();
-}
-
-bool WSLServer::has_peer(int p_id) const {
- return _peer_map.has(p_id);
-}
-
-Ref<WebSocketPeer> WSLServer::get_peer(int p_id) const {
- ERR_FAIL_COND_V(!has_peer(p_id), nullptr);
- return _peer_map[p_id];
-}
-
-IPAddress WSLServer::get_peer_address(int p_peer_id) const {
- ERR_FAIL_COND_V(!has_peer(p_peer_id), IPAddress());
-
- return _peer_map[p_peer_id]->get_connected_host();
-}
-
-int WSLServer::get_peer_port(int p_peer_id) const {
- ERR_FAIL_COND_V(!has_peer(p_peer_id), 0);
-
- return _peer_map[p_peer_id]->get_connected_port();
-}
-
-void WSLServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) {
- ERR_FAIL_COND(!has_peer(p_peer_id));
-
- get_peer(p_peer_id)->close(p_code, p_reason);
-}
-
-Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) {
- ERR_FAIL_COND_V_MSG(_server->is_listening(), FAILED, "Buffers sizes can only be set before listening or connecting.");
-
- _in_buf_size = nearest_shift(p_in_buffer - 1) + 10;
- _in_pkt_size = nearest_shift(p_in_packets - 1);
- _out_buf_size = nearest_shift(p_out_buffer - 1) + 10;
- _out_pkt_size = nearest_shift(p_out_packets - 1);
- return OK;
-}
-
-WSLServer::WSLServer() {
- _server.instantiate();
-}
-
-WSLServer::~WSLServer() {
- stop();
-}
-
-#endif // WEB_ENABLED
diff --git a/modules/websocket/wsl_server.h b/modules/websocket/wsl_server.h
deleted file mode 100644
index df0c1dc68a..0000000000
--- a/modules/websocket/wsl_server.h
+++ /dev/null
@@ -1,98 +0,0 @@
-/*************************************************************************/
-/* wsl_server.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 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 WSL_SERVER_H
-#define WSL_SERVER_H
-
-#ifndef WEB_ENABLED
-
-#include "websocket_server.h"
-#include "wsl_peer.h"
-
-#include "core/io/stream_peer_tcp.h"
-#include "core/io/stream_peer_tls.h"
-#include "core/io/tcp_server.h"
-
-class WSLServer : public WebSocketServer {
- GDCIIMPL(WSLServer, WebSocketServer);
-
-private:
- class PendingPeer : public RefCounted {
- private:
- bool _parse_request(const Vector<String> p_protocols, String &r_resource_name);
-
- public:
- Ref<StreamPeerTCP> tcp;
- Ref<StreamPeer> connection;
- bool use_tls = false;
-
- uint64_t time = 0;
- uint8_t req_buf[WSL_MAX_HEADER_SIZE] = {};
- int req_pos = 0;
- String key;
- String protocol;
- bool has_request = false;
- CharString response;
- int response_sent = 0;
-
- Error do_handshake(const Vector<String> p_protocols, uint64_t p_timeout, String &r_resource_name, const Vector<String> &p_extra_headers);
- };
-
- int _in_buf_size = DEF_BUF_SHIFT;
- int _in_pkt_size = DEF_PKT_SHIFT;
- int _out_buf_size = DEF_BUF_SHIFT;
- int _out_pkt_size = DEF_PKT_SHIFT;
-
- List<Ref<PendingPeer>> _pending;
- Ref<TCPServer> _server;
- Vector<String> _protocols;
- Vector<String> _extra_headers;
-
-public:
- Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) override;
- void set_extra_headers(const Vector<String> &p_headers) override;
- Error listen(int p_port, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false) override;
- void stop() override;
- bool is_listening() const override;
- int get_max_packet_size() const override;
- bool has_peer(int p_id) const override;
- Ref<WebSocketPeer> get_peer(int p_id) const override;
- IPAddress get_peer_address(int p_peer_id) const override;
- int get_peer_port(int p_peer_id) const override;
- void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = "") override;
- virtual void poll() override;
-
- WSLServer();
- ~WSLServer();
-};
-
-#endif // WEB_ENABLED
-
-#endif // WSL_SERVER_H
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index d0c7484aa1..6b671c1660 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -388,15 +388,15 @@ Projection WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p_a
int k = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
- eye.matrix[i][j] = js_matrix[k++];
+ eye.columns[i][j] = js_matrix[k++];
}
}
free(js_matrix);
// Copied from godot_oculus_mobile's ovr_mobile_session.cpp
- eye.matrix[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near);
- eye.matrix[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near);
+ eye.columns[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near);
+ eye.columns[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near);
return eye;
}