summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/csg/doc_classes/CSGShape3D.xml5
-rw-r--r--modules/enet/doc_classes/ENetMultiplayerPeer.xml3
-rw-r--r--modules/enet/doc_classes/ENetPacketPeer.xml26
-rw-r--r--modules/enet/enet_multiplayer_peer.cpp225
-rw-r--r--modules/enet/enet_multiplayer_peer.h20
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp2
-rw-r--r--modules/gdscript/gdscript.cpp5
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp14
-rw-r--r--modules/gdscript/gdscript_editor.cpp4
-rw-r--r--modules/gdscript/gdscript_parser.cpp16
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/glslang/register_types.cpp4
-rw-r--r--modules/gltf/gltf_document_extension.cpp42
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp6
-rw-r--r--modules/jpg/image_loader_jpegd.cpp38
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs8
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs8
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs9
-rw-r--r--modules/mono/editor/bindings_generator.cpp89
-rw-r--r--modules/mono/editor/code_completion.cpp2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs32
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs74
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs480
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs28
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs67
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs38
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs45
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj1
-rw-r--r--modules/mono/glue/runtime_interop.cpp12
-rw-r--r--modules/mono/godotsharp_dirs.cpp2
-rw-r--r--modules/mono/managed_callable.cpp5
-rw-r--r--modules/mono/managed_callable.h4
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h2
-rw-r--r--modules/multiplayer/doc_classes/SceneMultiplayer.xml4
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.cpp4
-rw-r--r--modules/multiplayer/scene_cache_interface.cpp6
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp163
-rw-r--r--modules/multiplayer/scene_multiplayer.h18
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp7
-rw-r--r--modules/multiplayer/scene_rpc_interface.cpp9
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp20
-rw-r--r--modules/openxr/editor/openxr_action_map_editor.cpp8
-rw-r--r--modules/openxr/editor/openxr_action_set_editor.cpp2
-rw-r--r--modules/regex/doc_classes/RegEx.xml2
-rw-r--r--modules/text_server_adv/SCsub7
-rw-r--r--modules/text_server_adv/text_server_adv.cpp73
-rw-r--r--modules/text_server_fb/text_server_fb.cpp48
-rw-r--r--modules/theora/video_stream_theora.cpp2
-rw-r--r--modules/upnp/doc_classes/UPNP.xml2
-rw-r--r--modules/webp/resource_saver_webp.cpp6
-rw-r--r--modules/webp/webp_common.cpp4
-rw-r--r--modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml39
-rw-r--r--modules/webrtc/webrtc_multiplayer_peer.cpp85
-rw-r--r--modules/webrtc/webrtc_multiplayer_peer.h20
-rw-r--r--modules/websocket/editor/editor_debugger_server_websocket.cpp4
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp254
-rw-r--r--modules/websocket/websocket_multiplayer_peer.h18
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml10
-rw-r--r--modules/webxr/godot_webxr.h2
-rw-r--r--modules/webxr/native/library_godot_webxr.js41
-rw-r--r--modules/webxr/webxr_interface_js.cpp3
73 files changed, 1476 insertions, 747 deletions
diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml
index 7e92d667b3..06f8f5a403 100644
--- a/modules/csg/doc_classes/CSGShape3D.xml
+++ b/modules/csg/doc_classes/CSGShape3D.xml
@@ -64,9 +64,10 @@
A contact is detected if object A is in any of the layers that object B scans, or object B is in any layer scanned by object A. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
</member>
<member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1">
- The physics layers this CSG shape scans for collisions. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
+ The physics layers this CSG shape scans for collisions. Only effective if [member use_collision] is [code]true[/code]. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
</member>
<member name="collision_priority" type="float" setter="set_collision_priority" getter="get_collision_priority" default="1.0">
+ The priority used to solve colliding when occurring penetration. Only effective if [member use_collision] is [code]true[/code]. The higher the priority is, the lower the penetration into the object will be. This can for example be used to prevent the player from breaking through the boundaries of a level.
</member>
<member name="operation" type="int" setter="set_operation" getter="get_operation" enum="CSGShape3D.Operation" default="0">
The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent.
@@ -75,7 +76,7 @@
Snap makes the mesh snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust.
</member>
<member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision" default="false">
- Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden.
+ Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority].
</member>
</members>
<constants>
diff --git a/modules/enet/doc_classes/ENetMultiplayerPeer.xml b/modules/enet/doc_classes/ENetMultiplayerPeer.xml
index 5181ae76ce..f02fc893fd 100644
--- a/modules/enet/doc_classes/ENetMultiplayerPeer.xml
+++ b/modules/enet/doc_classes/ENetMultiplayerPeer.xml
@@ -77,8 +77,5 @@
<member name="host" type="ENetConnection" setter="" getter="get_host">
The underlying [ENetConnection] created after [method create_client] and [method create_server].
</member>
- <member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true">
- Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server.
- </member>
</members>
</class>
diff --git a/modules/enet/doc_classes/ENetPacketPeer.xml b/modules/enet/doc_classes/ENetPacketPeer.xml
index 67760ba5e8..52f45b2338 100644
--- a/modules/enet/doc_classes/ENetPacketPeer.xml
+++ b/modules/enet/doc_classes/ENetPacketPeer.xml
@@ -80,7 +80,7 @@
<return type="void" />
<param index="0" name="ping_interval" type="int" />
<description>
- Sets the [code]ping_interval[/code] in milliseconds at which pings will be sent to a peer. Pings are used both to monitor the liveness of the connection and also to dynamically adjust the throttle during periods of low traffic so that the throttle has reasonable responsiveness during traffic spikes.
+ Sets the [code]ping_interval[/code] in milliseconds at which pings will be sent to a peer. Pings are used both to monitor the liveness of the connection and also to dynamically adjust the throttle during periods of low traffic so that the throttle has reasonable responsiveness during traffic spikes. The default ping interval is [code]500[/code] milliseconds.
</description>
</method>
<method name="reset">
@@ -115,33 +115,43 @@
<param index="2" name="deceleration" type="int" />
<description>
Configures throttle parameter for a peer.
- Unreliable packets are dropped by ENet in response to the varying conditions of the Internet connection to the peer. The throttle represents a probability that an unreliable packet should not be dropped and thus sent by ENet to the peer. By measuring fluctuations in round trip times of reliable packets over the specified [code]interval[/code], ENet will either increase the probably by the amount specified in the [code]acceleration[/code] parameter, or decrease it by the amount specified in the [code]deceleration[/code] parameter (both are ratios to [constant PACKET_THROTTLE_SCALE]).
+ Unreliable packets are dropped by ENet in response to the varying conditions of the Internet connection to the peer. The throttle represents a probability that an unreliable packet should not be dropped and thus sent by ENet to the peer. By measuring fluctuations in round trip times of reliable packets over the specified [code]interval[/code], ENet will either increase the probability by the amount specified in the [code]acceleration[/code] parameter, or decrease it by the amount specified in the [code]deceleration[/code] parameter (both are ratios to [constant PACKET_THROTTLE_SCALE]).
When the throttle has a value of [constant PACKET_THROTTLE_SCALE], no unreliable packets are dropped by ENet, and so 100% of all unreliable packets will be sent.
- When the throttle has a value of 0, all unreliable packets are dropped by ENet, and so 0% of all unreliable packets will be sent.
+ When the throttle has a value of [code]0[/code], all unreliable packets are dropped by ENet, and so 0% of all unreliable packets will be sent.
Intermediate values for the throttle represent intermediate probabilities between 0% and 100% of unreliable packets being sent. The bandwidth limits of the local and foreign hosts are taken into account to determine a sensible limit for the throttle probability above which it should not raise even in the best of conditions.
</description>
</method>
</methods>
<constants>
<constant name="STATE_DISCONNECTED" value="0" enum="PeerState">
+ The peer is disconnected.
</constant>
<constant name="STATE_CONNECTING" value="1" enum="PeerState">
+ The peer is currently attempting to connect.
</constant>
<constant name="STATE_ACKNOWLEDGING_CONNECT" value="2" enum="PeerState">
+ The peer has acknowledged the connection request.
</constant>
<constant name="STATE_CONNECTION_PENDING" value="3" enum="PeerState">
+ The peer is currently connecting.
</constant>
<constant name="STATE_CONNECTION_SUCCEEDED" value="4" enum="PeerState">
+ The peer has successfully connected, but is not ready to communicate with yet ([constant STATE_CONNECTED]).
</constant>
<constant name="STATE_CONNECTED" value="5" enum="PeerState">
+ The peer is currently connected and ready to communicate with.
</constant>
<constant name="STATE_DISCONNECT_LATER" value="6" enum="PeerState">
+ The peer is slated to disconnect after it has no more outgoing packets to send.
</constant>
<constant name="STATE_DISCONNECTING" value="7" enum="PeerState">
+ The peer is currently disconnecting.
</constant>
<constant name="STATE_ACKNOWLEDGING_DISCONNECT" value="8" enum="PeerState">
+ The peer has acknowledged the disconnection request.
</constant>
<constant name="STATE_ZOMBIE" value="9" enum="PeerState">
+ The peer has lost connection, but is not considered truly disconnected (as the peer didn't acknowledge the disconnection request).
</constant>
<constant name="PEER_PACKET_LOSS" value="0" enum="PeerStatistic">
Mean packet loss of reliable packets as a ratio with respect to the [constant PACKET_LOSS_SCALE].
@@ -150,6 +160,7 @@
Packet loss variance.
</constant>
<constant name="PEER_PACKET_LOSS_EPOCH" value="2" enum="PeerStatistic">
+ The time at which packet loss statistics were last updated (in milliseconds since the connection started). The interval for packet loss statistics updates is 10 seconds, and at least one packet must have been sent since the last statistics update.
</constant>
<constant name="PEER_ROUND_TRIP_TIME" value="3" enum="PeerStatistic">
Mean packet round trip time for reliable packets.
@@ -164,24 +175,31 @@
Variance of the last trip time recorded.
</constant>
<constant name="PEER_PACKET_THROTTLE" value="7" enum="PeerStatistic">
+ The peer's current throttle status.
</constant>
<constant name="PEER_PACKET_THROTTLE_LIMIT" value="8" enum="PeerStatistic">
+ The maximum number of unreliable packets that should not be dropped. This value is always greater than or equal to [code]1[/code]. The initial value is equal to [constant PACKET_THROTTLE_SCALE].
</constant>
<constant name="PEER_PACKET_THROTTLE_COUNTER" value="9" enum="PeerStatistic">
+ Internal value used to increment the packet throttle counter. The value is hardcoded to [code]7[/code] and cannot be changed. You probably want to look at [constant PEER_PACKET_THROTTLE_ACCELERATION] instead.
</constant>
<constant name="PEER_PACKET_THROTTLE_EPOCH" value="10" enum="PeerStatistic">
+ The time at which throttle statistics were last updated (in milliseconds since the connection started). The interval for throttle statistics updates is [constant PEER_PACKET_THROTTLE_INTERVAL].
</constant>
<constant name="PEER_PACKET_THROTTLE_ACCELERATION" value="11" enum="PeerStatistic">
+ The throttle's acceleration factor. Higher values will make ENet adapt to fluctuating network conditions faster, causing unrelaible packets to be sent [i]more[/i] often. The default value is [code]2[/code].
</constant>
<constant name="PEER_PACKET_THROTTLE_DECELERATION" value="12" enum="PeerStatistic">
+ The throttle's deceleration factor. Higher values will make ENet adapt to fluctuating network conditions faster, causing unrelaible packets to be sent [i]less[/i] often. The default value is [code]2[/code].
</constant>
<constant name="PEER_PACKET_THROTTLE_INTERVAL" value="13" enum="PeerStatistic">
+ The interval over which the lowest mean round trip time should be measured for use by the throttle mechanism (in milliseconds). The default value is [code]5000[/code].
</constant>
<constant name="PACKET_LOSS_SCALE" value="65536">
The reference scale for packet loss. See [method get_statistic] and [constant PEER_PACKET_LOSS].
</constant>
<constant name="PACKET_THROTTLE_SCALE" value="32">
- The reference value for throttle configuration. See [method throttle_configure].
+ The reference value for throttle configuration. The default value is [code]32[/code]. See [method throttle_configure].
</constant>
<constant name="FLAG_RELIABLE" value="1">
Mark the packet to be sent as reliable.
diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp
index e7728f4aec..c93db3b972 100644
--- a/modules/enet/enet_multiplayer_peer.cpp
+++ b/modules/enet/enet_multiplayer_peer.cpp
@@ -44,6 +44,22 @@ int ENetMultiplayerPeer::get_packet_peer() const {
return incoming_packets.front()->get().from;
}
+MultiplayerPeer::TransferMode ENetMultiplayerPeer::get_packet_mode() const {
+ ERR_FAIL_COND_V_MSG(!_is_active(), TRANSFER_MODE_RELIABLE, "The multiplayer instance isn't currently active.");
+ ERR_FAIL_COND_V(incoming_packets.size() == 0, TRANSFER_MODE_RELIABLE);
+ return incoming_packets.front()->get().transfer_mode;
+}
+
+int ENetMultiplayerPeer::get_packet_channel() const {
+ ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active.");
+ ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
+ int ch = incoming_packets.front()->get().channel;
+ if (ch >= SYSCH_MAX) { // First 2 channels are reserved.
+ return ch - SYSCH_MAX + 1;
+ }
+ return 0;
+}
+
Error ENetMultiplayerPeer::create_server(int p_port, int p_max_clients, int p_max_channels, int p_in_bandwidth, int p_out_bandwidth) {
ERR_FAIL_COND_V_MSG(_is_active(), ERR_ALREADY_IN_USE, "The multiplayer instance is already active.");
set_refuse_new_connections(false);
@@ -114,6 +130,22 @@ Error ENetMultiplayerPeer::add_mesh_peer(int p_id, Ref<ENetConnection> p_host) {
return OK;
}
+void ENetMultiplayerPeer::_store_packet(int32_t p_source, ENetConnection::Event &p_event) {
+ Packet packet;
+ packet.packet = p_event.packet;
+ packet.channel = p_event.channel_id;
+ packet.from = p_source;
+ if (p_event.packet->flags & ENET_PACKET_FLAG_RELIABLE) {
+ packet.transfer_mode = TRANSFER_MODE_RELIABLE;
+ } else if (p_event.packet->flags & ENET_PACKET_FLAG_UNSEQUENCED) {
+ packet.transfer_mode = TRANSFER_MODE_UNRELIABLE;
+ } else {
+ packet.transfer_mode = TRANSFER_MODE_UNRELIABLE_ORDERED;
+ }
+ packet.packet->referenceCount++;
+ incoming_packets.push_back(packet);
+}
+
bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event) {
switch (p_type) {
case ENetConnection::EVENT_CONNECT: {
@@ -131,9 +163,6 @@ bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type,
peers[id] = p_event.peer;
emit_signal(SNAME("peer_connected"), id);
- if (server_relay) {
- _notify_peers(id, true);
- }
return false;
}
case ENetConnection::EVENT_DISCONNECT: {
@@ -145,50 +174,11 @@ bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type,
emit_signal(SNAME("peer_disconnected"), id);
peers.erase(id);
- if (server_relay) {
- _notify_peers(id, false);
- }
return false;
}
case ENetConnection::EVENT_RECEIVE: {
- if (p_event.channel_id == SYSCH_CONFIG) {
- _destroy_unused(p_event.packet);
- ERR_FAIL_V_MSG(false, "Only server can send config messages");
- } else {
- if (p_event.packet->dataLength < 8) {
- _destroy_unused(p_event.packet);
- ERR_FAIL_V_MSG(false, "Invalid packet size");
- }
-
- uint32_t source = decode_uint32(&p_event.packet->data[0]);
- int target = decode_uint32(&p_event.packet->data[4]);
-
- uint32_t id = p_event.peer->get_meta(SNAME("_net_id"));
- // Someone is cheating and trying to fake the source!
- if (source != id) {
- _destroy_unused(p_event.packet);
- ERR_FAIL_V_MSG(false, "Someone is cheating and trying to fake the source!");
- }
-
- Packet packet;
- packet.packet = p_event.packet;
- packet.channel = p_event.channel_id;
- packet.from = id;
-
- // Even if relaying is disabled, these targets are valid as incoming packets.
- if (target == 1 || target == 0 || target < -1) {
- packet.packet->referenceCount++;
- incoming_packets.push_back(packet);
- }
-
- if (server_relay && target != 1) {
- packet.packet->referenceCount++;
- _relay(source, target, p_event.channel_id, p_event.packet);
- packet.packet->referenceCount--;
- _destroy_unused(p_event.packet);
- }
- // Destroy packet later
- }
+ int32_t source = p_event.peer->get_meta(SNAME("_net_id"));
+ _store_packet(source, p_event);
return false;
}
default:
@@ -215,44 +205,7 @@ bool ENetMultiplayerPeer::_parse_client_event(ENetConnection::EventType p_type,
return true;
}
case ENetConnection::EVENT_RECEIVE: {
- if (p_event.channel_id == SYSCH_CONFIG) {
- // Config message
- if (p_event.packet->dataLength != 8) {
- _destroy_unused(p_event.packet);
- ERR_FAIL_V(false);
- }
-
- int msg = decode_uint32(&p_event.packet->data[0]);
- int id = decode_uint32(&p_event.packet->data[4]);
-
- switch (msg) {
- case SYSMSG_ADD_PEER: {
- peers[id] = Ref<ENetPacketPeer>();
- emit_signal(SNAME("peer_connected"), id);
-
- } break;
- case SYSMSG_REMOVE_PEER: {
- peers.erase(id);
- emit_signal(SNAME("peer_disconnected"), id);
- } break;
- }
- _destroy_unused(p_event.packet);
- } else {
- if (p_event.packet->dataLength < 8) {
- _destroy_unused(p_event.packet);
- ERR_FAIL_V_MSG(false, "Invalid packet size");
- }
-
- uint32_t source = decode_uint32(&p_event.packet->data[0]);
- Packet packet;
- packet.packet = p_event.packet;
- packet.from = source;
- packet.channel = p_event.channel_id;
-
- packet.packet->referenceCount++;
- incoming_packets.push_back(packet);
- // Destroy packet later
- }
+ _store_packet(1, p_event);
return false;
}
default:
@@ -273,18 +226,7 @@ bool ENetMultiplayerPeer::_parse_mesh_event(ENetConnection::EventType p_type, EN
hosts.erase(p_peer_id);
return true;
case ENetConnection::EVENT_RECEIVE: {
- if (p_event.packet->dataLength < 8) {
- _destroy_unused(p_event.packet);
- ERR_FAIL_V_MSG(false, "Invalid packet size");
- }
-
- Packet packet;
- packet.packet = p_event.packet;
- packet.from = p_peer_id;
- packet.channel = p_event.channel_id;
-
- packet.packet->referenceCount++;
- incoming_packets.push_back(packet);
+ _store_packet(p_peer_id, p_event);
return false;
} break;
default:
@@ -376,6 +318,10 @@ bool ENetMultiplayerPeer::is_server() const {
return active_mode == MODE_SERVER;
}
+bool ENetMultiplayerPeer::is_server_relay_supported() const {
+ return active_mode == MODE_SERVER || active_mode == MODE_CLIENT;
+}
+
void ENetMultiplayerPeer::close_connection(uint32_t wait_usec) {
if (!_is_active()) {
return;
@@ -422,8 +368,8 @@ Error ENetMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_si
current_packet = incoming_packets.front()->get();
incoming_packets.pop_front();
- *r_buffer = (const uint8_t *)(&current_packet.packet->data[8]);
- r_buffer_size = current_packet.packet->dataLength - 8;
+ *r_buffer = (const uint8_t *)(current_packet.packet->data);
+ r_buffer_size = current_packet.packet->dataLength;
return OK;
}
@@ -457,15 +403,13 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size
}
#ifdef DEBUG_ENABLED
- if ((packet_flags & ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) && p_buffer_size + 8 > ENET_HOST_DEFAULT_MTU) {
- WARN_PRINT_ONCE(vformat("Sending %d bytes unrealiably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size + 8, ENET_HOST_DEFAULT_MTU));
+ if ((packet_flags & ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) && p_buffer_size > ENET_HOST_DEFAULT_MTU) {
+ WARN_PRINT_ONCE(vformat("Sending %d bytes unrealiably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size, ENET_HOST_DEFAULT_MTU));
}
#endif
- ENetPacket *packet = enet_packet_create(nullptr, p_buffer_size + 8, packet_flags);
- encode_uint32(unique_id, &packet->data[0]); // Source ID
- encode_uint32(target_peer, &packet->data[4]); // Dest ID
- memcpy(&packet->data[8], p_buffer, p_buffer_size);
+ ENetPacket *packet = enet_packet_create(nullptr, p_buffer_size, packet_flags);
+ memcpy(&packet->data[0], p_buffer, p_buffer_size);
if (is_server()) {
if (target_peer == 0) {
@@ -548,16 +492,6 @@ void ENetMultiplayerPeer::set_refuse_new_connections(bool p_enabled) {
MultiplayerPeer::set_refuse_new_connections(p_enabled);
}
-void ENetMultiplayerPeer::set_server_relay_enabled(bool p_enabled) {
- ERR_FAIL_COND_MSG(_is_active(), "Server relaying can't be toggled while the multiplayer instance is active.");
-
- server_relay = p_enabled;
-}
-
-bool ENetMultiplayerPeer::is_server_relay_enabled() const {
- return server_relay;
-}
-
Ref<ENetConnection> ENetMultiplayerPeer::get_host() const {
ERR_FAIL_COND_V(!_is_active(), nullptr);
ERR_FAIL_COND_V(active_mode == MODE_MESH, nullptr);
@@ -577,70 +511,6 @@ void ENetMultiplayerPeer::_destroy_unused(ENetPacket *p_packet) {
}
}
-void ENetMultiplayerPeer::_relay(int p_from, int p_to, enet_uint8 p_channel, ENetPacket *p_packet) {
- if (p_to == 0) {
- // Re-send to everyone but sender :|
- for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
- if (E.key == p_from) {
- continue;
- }
-
- E.value->send(p_channel, p_packet);
- }
- } else if (p_to < 0) {
- // Re-send to everyone but excluded and sender.
- for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
- if (E.key == p_from || E.key == -p_to) { // Do not resend to self, also do not send to excluded
- continue;
- }
-
- E.value->send(p_channel, p_packet);
- }
- } else {
- // To someone else, specifically
- ERR_FAIL_COND(!peers.has(p_to));
- ENetPacket *packet = enet_packet_create(p_packet->data, p_packet->dataLength, p_packet->flags);
- peers[p_to]->send(p_channel, packet);
- }
-}
-
-void ENetMultiplayerPeer::_notify_peers(int p_id, bool p_connected) {
- if (p_connected) {
- ERR_FAIL_COND(!peers.has(p_id));
- // Someone connected, notify all the peers available.
- Ref<ENetPacketPeer> peer = peers[p_id];
- ENetPacket *packet = enet_packet_create(nullptr, 8, ENET_PACKET_FLAG_RELIABLE);
- encode_uint32(SYSMSG_ADD_PEER, &packet->data[0]);
- encode_uint32(p_id, &packet->data[4]);
- for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
- if (E.key == p_id) {
- continue;
- }
- // Send new peer to existing peer.
- E.value->send(SYSCH_CONFIG, packet);
- // Send existing peer to new peer.
- // This packet will be automatically destroyed by ENet after send.
- ENetPacket *packet2 = enet_packet_create(nullptr, 8, ENET_PACKET_FLAG_RELIABLE);
- encode_uint32(SYSMSG_ADD_PEER, &packet2->data[0]);
- encode_uint32(E.key, &packet2->data[4]);
- peer->send(SYSCH_CONFIG, packet2);
- }
- _destroy_unused(packet);
- } else {
- // Server just received a client disconnect and is in relay mode, notify everyone else.
- ENetPacket *packet = enet_packet_create(nullptr, 8, ENET_PACKET_FLAG_RELIABLE);
- encode_uint32(SYSMSG_REMOVE_PEER, &packet->data[0]);
- encode_uint32(p_id, &packet->data[4]);
- for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
- if (E.key == p_id) {
- continue;
- }
- E.value->send(SYSCH_CONFIG, packet);
- }
- _destroy_unused(packet);
- }
-}
-
void ENetMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_server", "port", "max_clients", "max_channels", "in_bandwidth", "out_bandwidth"), &ENetMultiplayerPeer::create_server, DEFVAL(32), DEFVAL(0), DEFVAL(0), DEFVAL(0));
ClassDB::bind_method(D_METHOD("create_client", "address", "port", "channel_count", "in_bandwidth", "out_bandwidth", "local_port"), &ENetMultiplayerPeer::create_client, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0));
@@ -649,12 +519,9 @@ void ENetMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("close_connection", "wait_usec"), &ENetMultiplayerPeer::close_connection, DEFVAL(100));
ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &ENetMultiplayerPeer::set_bind_ip);
- ClassDB::bind_method(D_METHOD("set_server_relay_enabled", "enabled"), &ENetMultiplayerPeer::set_server_relay_enabled);
- ClassDB::bind_method(D_METHOD("is_server_relay_enabled"), &ENetMultiplayerPeer::is_server_relay_enabled);
ClassDB::bind_method(D_METHOD("get_host"), &ENetMultiplayerPeer::get_host);
ClassDB::bind_method(D_METHOD("get_peer", "id"), &ENetMultiplayerPeer::get_peer);
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "host", PROPERTY_HINT_RESOURCE_TYPE, "ENetConnection", PROPERTY_USAGE_NONE), "", "get_host");
}
diff --git a/modules/enet/enet_multiplayer_peer.h b/modules/enet/enet_multiplayer_peer.h
index 3152068d46..85692e8eee 100644
--- a/modules/enet/enet_multiplayer_peer.h
+++ b/modules/enet/enet_multiplayer_peer.h
@@ -47,10 +47,9 @@ private:
};
enum {
- SYSCH_CONFIG = 0,
- SYSCH_RELIABLE = 1,
- SYSCH_UNRELIABLE = 2,
- SYSCH_MAX = 3
+ SYSCH_RELIABLE = 0,
+ SYSCH_UNRELIABLE = 1,
+ SYSCH_MAX = 2
};
enum Mode {
@@ -66,8 +65,6 @@ private:
int target_peer = 0;
- bool server_relay = true;
-
ConnectionStatus connection_status = CONNECTION_DISCONNECTED;
HashMap<int, Ref<ENetConnection>> hosts;
@@ -77,18 +74,18 @@ private:
ENetPacket *packet = nullptr;
int from = 0;
int channel = 0;
+ TransferMode transfer_mode = TRANSFER_MODE_RELIABLE;
};
List<Packet> incoming_packets;
Packet current_packet;
+ void _store_packet(int32_t p_source, ENetConnection::Event &p_event);
void _pop_current_packet();
bool _parse_server_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event);
bool _parse_client_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event);
bool _parse_mesh_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event, int p_peer_id);
- void _relay(int p_from, int p_to, enet_uint8 p_channel, ENetPacket *p_packet);
- void _notify_peers(int p_id, bool p_connected);
void _destroy_unused(ENetPacket *p_packet);
_FORCE_INLINE_ bool _is_active() const { return active_mode != MODE_NONE; }
@@ -99,10 +96,15 @@ protected:
public:
virtual void set_target_peer(int p_peer) override;
+
virtual int get_packet_peer() const override;
+ virtual TransferMode get_packet_mode() const override;
+ virtual int get_packet_channel() const override;
virtual void poll() override;
virtual bool is_server() const override;
+ virtual bool is_server_relay_supported() const override;
+
// Overridden so we can instrument the DTLSServer when needed.
virtual void set_refuse_new_connections(bool p_enabled) override;
@@ -125,8 +127,6 @@ public:
void disconnect_peer(int p_peer, bool now = false);
void set_bind_ip(const IPAddress &p_ip);
- void set_server_relay_enabled(bool p_enabled);
- bool is_server_relay_enabled() const;
Ref<ENetConnection> get_host() const;
Ref<ENetPacketPeer> get_peer(int p_id) const;
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 578e7a34f3..8246c96c15 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -4,7 +4,7 @@
A script implemented in the GDScript programming language.
</brief_description>
<description>
- A script implemented in the GDScript programming language. The script extends the functionality of all objects that instance it.
+ A script implemented in the GDScript programming language. The script extends the functionality of all objects that instantiate it.
[method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes.
</description>
<tutorials>
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 996d323a7f..420401cb79 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -683,7 +683,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
}
- const String text_edit_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme");
+ const String text_edit_color_theme = EDITOR_GET("text_editor/theme/color_theme");
const bool godot_2_theme = text_edit_color_theme == "Godot 2";
if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) {
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 0a9dad04c7..b4da9c1224 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1050,6 +1050,11 @@ Error GDScript::load_byte_code(const String &p_path) {
return ERR_COMPILATION_FAILED;
}
+void GDScript::set_path(const String &p_path, bool p_take_over) {
+ Script::set_path(p_path, p_take_over);
+ this->path = p_path;
+}
+
Error GDScript::load_source_code(const String &p_path) {
Vector<uint8_t> sourcef;
Error err;
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 0a010e5ad0..72ad890fbc 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -222,6 +222,7 @@ public:
virtual Error reload(bool p_keep_state = false) override;
+ virtual void set_path(const String &p_path, bool p_take_over = false) override;
void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too...
Error load_source_code(const String &p_path);
Error load_byte_code(const String &p_path);
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 898e4eb1a6..1401e4b94b 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -1602,8 +1602,8 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) {
reduce_expression(p_assert->condition);
if (p_assert->message != nullptr) {
reduce_expression(p_assert->message);
- if (!p_assert->message->is_constant || p_assert->message->reduced_value.get_type() != Variant::STRING) {
- push_error(R"(Expected constant string for assert error message.)", p_assert->message);
+ if (!p_assert->message->get_datatype().has_no_type() && (p_assert->message->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_assert->message->get_datatype().builtin_type != Variant::STRING)) {
+ push_error(R"(Expected string for assert error message.)", p_assert->message);
}
}
@@ -2425,9 +2425,15 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
switch (err.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
- PropertyInfo wrong_arg = function_info.arguments[err.argument];
+ String expected_type_name;
+ if (err.argument < function_info.arguments.size()) {
+ expected_type_name = type_from_property(function_info.arguments[err.argument]).to_string();
+ } else {
+ expected_type_name = Variant::get_type_name((Variant::Type)err.expected);
+ }
+
push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1,
- type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()),
+ expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()),
p_call->arguments[err.argument]);
} break;
case Callable::CallError::CALL_ERROR_INVALID_METHOD:
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 3c68993b36..f33d1409bc 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2513,7 +2513,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) {
if (p_call->type == GDScriptParser::Node::PRELOAD) {
- if (p_argidx == 0 && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
+ if (p_argidx == 0 && bool(EDITOR_GET("text_editor/completion/complete_file_paths"))) {
_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result);
}
@@ -2820,7 +2820,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
r_forced = true;
} break;
case GDScriptParser::COMPLETION_RESOURCE_PATH: {
- if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
+ if (EDITOR_GET("text_editor/completion/complete_file_paths")) {
_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
r_forced = true;
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index bdf6fb35b6..4279edf394 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3765,13 +3765,19 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
break;
case GDScriptParser::DataType::CLASS:
// Can assume type is a global GDScript class.
- if (!ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
- push_error(R"(Exported script type must extend Resource.)");
+ if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ variable->export_info.hint_string = export_type.class_type->identifier->name;
+ } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
+ variable->export_info.hint_string = export_type.class_type->identifier->name;
+ } else {
+ push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable);
return false;
}
- variable->export_info.type = Variant::OBJECT;
- variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
- variable->export_info.hint_string = export_type.class_type->identifier->name;
+
break;
case GDScriptParser::DataType::SCRIPT: {
StringName class_name;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 1850a44678..f40887ddb8 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -469,7 +469,7 @@ public:
EnumNode *parent_enum = nullptr;
int index = -1;
bool resolved = false;
- int value = 0;
+ int64_t value = 0;
int line = 0;
int leftmost_column = 0;
int rightmost_column = 0;
diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp
index b1c2140039..53ec0fde34 100644
--- a/modules/glslang/register_types.cpp
+++ b/modules/glslang/register_types.cpp
@@ -53,7 +53,6 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
};
int ClientInputSemanticsVersion = 100; // maps to, say, #define VULKAN 100
- bool check_subgroup_support = true; // assume we support subgroups
glslang::EShTargetClientVersion ClientVersion = glslang::EShTargetVulkan_1_2;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_5;
@@ -63,7 +62,6 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
if (capabilities->version_major == 1 && capabilities->version_minor == 0) {
ClientVersion = glslang::EShTargetVulkan_1_0;
TargetVersion = glslang::EShTargetSpv_1_0;
- check_subgroup_support = false; // subgroups are not supported in Vulkan 1.0
} else if (capabilities->version_major == 1 && capabilities->version_minor == 1) {
ClientVersion = glslang::EShTargetVulkan_1_1;
TargetVersion = glslang::EShTargetSpv_1_3;
@@ -88,7 +86,7 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
shader.setEnvClient(glslang::EShClientVulkan, ClientVersion);
shader.setEnvTarget(glslang::EShTargetSpv, TargetVersion);
- if (check_subgroup_support) {
+ {
uint32_t stage_bit = 1 << p_stage;
uint32_t subgroup_in_shaders = uint32_t(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS));
diff --git a/modules/gltf/gltf_document_extension.cpp b/modules/gltf/gltf_document_extension.cpp
index 3b952f8246..713779712c 100644
--- a/modules/gltf/gltf_document_extension.cpp
+++ b/modules/gltf/gltf_document_extension.cpp
@@ -51,45 +51,35 @@ Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
int err = OK;
- if (GDVIRTUAL_CALL(_import_post, p_state, p_root, err)) {
- return Error(err);
- }
- return OK;
+ GDVIRTUAL_CALL(_import_post, p_state, p_root, err);
+ return Error(err);
}
Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
int err = OK;
- if (GDVIRTUAL_CALL(_import_preflight, p_state, err)) {
- return Error(err);
- }
- return OK;
+ GDVIRTUAL_CALL(_import_preflight, p_state, err);
+ return Error(err);
}
Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
int err = OK;
- if (GDVIRTUAL_CALL(_import_post_parse, p_state, err)) {
- return Error(err);
- }
- return OK;
+ GDVIRTUAL_CALL(_import_post_parse, p_state, err);
+ return Error(err);
}
Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
int err = OK;
- if (GDVIRTUAL_CALL(_export_post, p_state, err)) {
- return Error(err);
- }
- return OK;
+ GDVIRTUAL_CALL(_export_post, p_state, err);
+ return Error(err);
}
Error GLTFDocumentExtension::export_preflight(Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
int err = OK;
- if (GDVIRTUAL_CALL(_export_preflight, p_root, err)) {
- return Error(err);
- }
- return OK;
+ GDVIRTUAL_CALL(_export_preflight, p_root, err);
+ return Error(err);
}
Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
@@ -97,10 +87,8 @@ Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p
ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
int err = OK;
- if (GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err)) {
- return Error(err);
- }
- return OK;
+ GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err);
+ return Error(err);
}
Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
@@ -108,8 +96,6 @@ Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p
ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
int err = OK;
- if (GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err)) {
- return Error(err);
- }
- return OK;
+ GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err);
+ return Error(err);
}
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index b5afd8507d..9c6cbebf0e 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -617,7 +617,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
}
if (mb->is_pressed()) {
- Node3DEditorViewport::NavigationScheme nav_scheme = (Node3DEditorViewport::NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
+ Node3DEditorViewport::NavigationScheme nav_scheme = (Node3DEditorViewport::NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
if ((nav_scheme == Node3DEditorViewport::NAVIGATION_MAYA || nav_scheme == Node3DEditorViewport::NAVIGATION_MODO) && mb->is_alt_pressed()) {
input_action = INPUT_NONE;
} else if (mb->get_button_index() == MouseButton::LEFT) {
@@ -1434,7 +1434,7 @@ GridMapEditor::~GridMapEditor() {
void GridMapEditorPlugin::_notification(int p_what) {
switch (p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) {
+ switch ((int)EDITOR_GET("editors/grid_map/editor_side")) {
case 0: { // Left.
Node3DEditor::get_singleton()->move_control_to_left_panel(grid_map_editor);
} break;
@@ -1472,7 +1472,7 @@ GridMapEditorPlugin::GridMapEditorPlugin() {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/grid_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right"));
grid_map_editor = memnew(GridMapEditor);
- switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) {
+ switch ((int)EDITOR_GET("editors/grid_map/editor_side")) {
case 0: { // Left.
Node3DEditor::get_singleton()->add_control_to_left_panel(grid_map_editor);
} break;
diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp
index d465467cf9..9bbe467352 100644
--- a/modules/jpg/image_loader_jpegd.cpp
+++ b/modules/jpg/image_loader_jpegd.cpp
@@ -33,8 +33,8 @@
#include "core/os/os.h"
#include "core/string/print_string.h"
-#include "thirdparty/jpeg-compressor/jpgd.h"
-#include "thirdparty/jpeg-compressor/jpge.h"
+#include <jpgd.h>
+#include <jpge.h>
#include <string.h>
Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) {
@@ -132,10 +132,6 @@ static Ref<Image> _jpegd_mem_loader_func(const uint8_t *p_png, int p_size) {
return img;
}
-static Error _jpgd_save_func(const String &p_path, const Ref<Image> &p_img, float p_quality) {
- return OK;
-}
-
class ImageLoaderJPGOSFile : public jpge::output_stream {
public:
Ref<FileAccess> f;
@@ -157,21 +153,18 @@ public:
}
};
-static Vector<uint8_t> _jpgd_buffer_save_func(const Ref<Image> &p_img, float p_quality) {
- ERR_FAIL_COND_V(p_img.is_null() || p_img->is_empty(), Vector<uint8_t>());
+static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, const Ref<Image> &p_img, float p_quality) {
+ ERR_FAIL_COND_V(p_img.is_null() || p_img->is_empty(), ERR_INVALID_PARAMETER);
Ref<Image> image = p_img;
if (image->get_format() != Image::FORMAT_RGB8) {
- image->convert(Image::FORMAT_ETC2_RGB8);
+ image->convert(Image::FORMAT_RGB8);
}
jpge::params p;
p.m_quality = CLAMP(p_quality * 100, 1, 100);
- Vector<uint8_t> output;
- ImageLoaderJPGOSBuffer ob;
- ob.buffer = &output;
jpge::jpeg_encoder enc;
- enc.init(&ob, image->get_width(), image->get_height(), 3, p);
+ enc.init(p_output_stream, image->get_width(), image->get_height(), 3, p);
const uint8_t *src_data = image->get_data().ptr();
for (int i = 0; i < image->get_height(); i++) {
@@ -180,9 +173,28 @@ static Vector<uint8_t> _jpgd_buffer_save_func(const Ref<Image> &p_img, float p_q
enc.process_scanline(nullptr);
+ return OK;
+}
+
+static Vector<uint8_t> _jpgd_buffer_save_func(const Ref<Image> &p_img, float p_quality) {
+ Vector<uint8_t> output;
+ ImageLoaderJPGOSBuffer ob;
+ ob.buffer = &output;
+ if (_jpgd_save_to_output_stream(&ob, p_img, p_quality) != OK) {
+ return Vector<uint8_t>();
+ }
return output;
}
+static Error _jpgd_save_func(const String &p_path, const Ref<Image> &p_img, float p_quality) {
+ Error err;
+ Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
+ ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save JPG at path: '%s'.", p_path));
+ ImageLoaderJPGOSFile ob;
+ ob.f = file;
+ return _jpgd_save_to_output_stream(&ob, p_img, p_quality);
+}
+
ImageLoaderJPG::ImageLoaderJPG() {
Image::_jpg_mem_loader_func = _jpegd_mem_loader_func;
Image::save_jpg_func = _jpgd_save_func;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
index 7ec3f88e5d..19fdd51dab 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
@@ -56,7 +56,7 @@ namespace GodotPlugins.Game
}
";
- context.AddSource("GodotPlugins.Game_Generated",
+ context.AddSource("GodotPlugins.Game.generated",
SourceText.From(source, Encoding.UTF8));
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
index 1ee31eb1a9..d5d80df643 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
@@ -87,7 +87,7 @@ namespace Godot.SourceGenerators
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
- + "_ScriptMethods_Generated";
+ + "_ScriptMethods.generated";
var source = new StringBuilder();
@@ -188,14 +188,14 @@ namespace Godot.SourceGenerators
if (godotClassMethods.Length > 0)
{
source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, ");
- source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n");
+ source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n");
foreach (var method in godotClassMethods)
{
GenerateMethodInvoker(method, source);
}
- source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n");
+ source.Append(" return base.InvokeGodotClassMethod(method, args, out ret);\n");
source.Append(" }\n");
}
@@ -364,7 +364,7 @@ namespace Godot.SourceGenerators
source.Append(" if (method == MethodName.");
source.Append(methodName);
- source.Append(" && argCount == ");
+ source.Append(" && args.Count == ");
source.Append(method.ParamTypes.Length);
source.Append(") {\n");
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
index e8a9e28d0c..ccfb405d26 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
@@ -96,8 +96,8 @@ namespace Godot.SourceGenerators
string.Empty;
bool hasNamespace = classNs.Length != 0;
- var uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
- + "_ScriptPath_Generated";
+ string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ + "_ScriptPath.generated";
var source = new StringBuilder();
@@ -126,7 +126,7 @@ namespace Godot.SourceGenerators
source.Append("\n}\n");
}
- context.AddSource(uniqueHint.ToString(), SourceText.From(source.ToString(), Encoding.UTF8));
+ context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context,
@@ -157,7 +157,7 @@ namespace Godot.SourceGenerators
sourceBuilder.Append("})]\n");
- context.AddSource("AssemblyScriptTypes_Generated",
+ context.AddSource("AssemblyScriptTypes.generated",
SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index b331e2e794..1198c633d9 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -73,7 +73,7 @@ namespace Godot.SourceGenerators
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
- + "_ScriptProperties_Generated";
+ + "_ScriptProperties.generated";
var source = new StringBuilder();
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
index 65dadcb801..98b9745c16 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
@@ -73,7 +73,7 @@ namespace Godot.SourceGenerators
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
- + "_ScriptPropertyDefVal_Generated";
+ + "_ScriptPropertyDefVal.generated";
var source = new StringBuilder();
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
index a40220565f..11e0a6fa21 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
@@ -73,7 +73,7 @@ namespace Godot.SourceGenerators
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
- + "_ScriptSerialization_Generated";
+ + "_ScriptSerialization.generated";
var source = new StringBuilder();
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
index ea7cc3fe38..50196b84f0 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
@@ -82,7 +82,7 @@ namespace Godot.SourceGenerators
bool isInnerClass = symbol.ContainingType != null;
string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
- + "_ScriptSignals_Generated";
+ + "_ScriptSignals.generated";
var source = new StringBuilder();
@@ -167,6 +167,7 @@ namespace Godot.SourceGenerators
Common.ReportSignalDelegateSignatureMustReturnVoid(context, signalDelegateSymbol);
}
}
+
continue;
}
@@ -257,14 +258,14 @@ namespace Godot.SourceGenerators
{
source.Append(
" protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
- source.Append("NativeVariantPtrArgs args, int argCount)\n {\n");
+ source.Append("NativeVariantPtrArgs args)\n {\n");
foreach (var signal in godotSignalDelegates)
{
GenerateSignalEventInvoker(signal, source);
}
- source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n");
+ source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args);\n");
source.Append(" }\n");
}
@@ -404,7 +405,7 @@ namespace Godot.SourceGenerators
source.Append(" if (signal == SignalName.");
source.Append(signalName);
- source.Append(" && argCount == ");
+ source.Append(" && args.Count == ");
source.Append(invokeMethodData.ParamTypes.Length);
source.Append(") {\n");
source.Append(" backing_");
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 3be8dd87c0..b90321b586 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -1614,7 +1614,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")
<< " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, "
- << "NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n"
+ << "NativeVariantPtrArgs args, out godot_variant ret)\n"
<< INDENT1 "{\n";
for (const MethodInterface &imethod : itype.methods) {
@@ -1630,7 +1630,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
// We check both native names (snake_case) and proxy names (PascalCase)
output << INDENT2 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
<< " || method == MethodName." << imethod.proxy_name
- << ") && argCount == " << itos(imethod.arguments.size())
+ << ") && args.Count == " << itos(imethod.arguments.size())
<< " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)"
<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n"
<< INDENT2 "{\n";
@@ -1682,7 +1682,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
if (is_derived_type) {
- output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, argCount, out ret);\n";
+ output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, out ret);\n";
} else {
output << INDENT2 "ret = default;\n"
<< INDENT2 "return false;\n";
@@ -2200,6 +2200,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) {
String arguments_sig;
+ String delegate_type_params;
+
+ if (!p_isignal.arguments.is_empty()) {
+ delegate_type_params += "<";
+ }
// Retrieve information from the arguments
const ArgumentInterface &first = p_isignal.arguments.front()->get();
@@ -2220,11 +2225,18 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
if (&iarg != &first) {
arguments_sig += ", ";
+ delegate_type_params += ", ";
}
arguments_sig += arg_type->cs_type;
arguments_sig += " ";
arguments_sig += iarg.name;
+
+ delegate_type_params += arg_type->cs_type;
+ }
+
+ if (!p_isignal.arguments.is_empty()) {
+ delegate_type_params += ">";
}
// Generate signal
@@ -2248,15 +2260,46 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append("\")]");
}
- String delegate_name = p_isignal.proxy_name;
- delegate_name += "EventHandler"; // Delegate name is [SignalName]EventHandler
+ bool is_parameterless = p_isignal.arguments.size() == 0;
- // Generate delegate
- p_output.append(MEMBER_BEGIN "public delegate void ");
- p_output.append(delegate_name);
- p_output.append("(");
- p_output.append(arguments_sig);
- p_output.append(");\n");
+ // Delegate name is [SignalName]EventHandler
+ String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler";
+
+ if (!is_parameterless) {
+ // Generate delegate
+ p_output.append(MEMBER_BEGIN "public delegate void ");
+ p_output.append(delegate_name);
+ p_output.append("(");
+ p_output.append(arguments_sig);
+ p_output.append(");\n");
+
+ // Generate Callable trampoline for the delegate
+ p_output << MEMBER_BEGIN "private static unsafe void " << p_isignal.proxy_name << "Trampoline"
+ << "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n"
+ << INDENT1 "{\n"
+ << INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n"
+ << INDENT2 "((" << delegate_name << ")delegateObj)(";
+
+ int idx = 0;
+ for (const ArgumentInterface &iarg : p_isignal.arguments) {
+ const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+ ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found
+
+ if (idx != 0) {
+ p_output << ",";
+ }
+
+ // TODO: We don't need to use VariantConversionCallbacks. We have the type information so we can use [cs_variant_to_managed] and [cs_managed_to_variant].
+ p_output << "\n" INDENT3 "VariantConversionCallbacks.GetToManagedCallback<"
+ << arg_type->cs_type << ">()(args[" << itos(idx) << "])";
+
+ idx++;
+ }
+
+ p_output << ");\n"
+ << INDENT2 "ret = default;\n"
+ << INDENT1 "}\n";
+ }
if (p_isignal.method_doc && p_isignal.method_doc->description.size()) {
String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype);
@@ -2292,6 +2335,11 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append("static ");
}
+ if (!is_parameterless) {
+ // `unsafe` is needed for taking the trampoline's function pointer
+ p_output << "unsafe ";
+ }
+
p_output.append("event ");
p_output.append(delegate_name);
p_output.append(" ");
@@ -2304,8 +2352,13 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append("add => Connect(SignalName.");
}
- p_output.append(p_isignal.proxy_name);
- p_output.append(", new Callable(value));\n");
+ if (is_parameterless) {
+ // Delegate type is Action. No need for custom trampoline.
+ p_output << p_isignal.proxy_name << ", Callable.From(value));\n";
+ } else {
+ p_output << p_isignal.proxy_name
+ << ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";
+ }
if (p_itype.is_singleton) {
p_output.append(INDENT2 "remove => " CS_PROPERTY_SINGLETON ".Disconnect(SignalName.");
@@ -2313,8 +2366,14 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append(INDENT2 "remove => Disconnect(SignalName.");
}
- p_output.append(p_isignal.proxy_name);
- p_output.append(", new Callable(value));\n");
+ if (is_parameterless) {
+ // Delegate type is Action. No need for custom trampoline.
+ p_output << p_isignal.proxy_name << ", Callable.From(value));\n";
+ } else {
+ p_output << p_isignal.proxy_name
+ << ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";
+ }
+
p_output.append(CLOSE_BLOCK_L1);
}
diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp
index 40296eef10..dc69567261 100644
--- a/modules/mono/editor/code_completion.cpp
+++ b/modules/mono/editor/code_completion.cpp
@@ -140,7 +140,7 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr
}
} break;
case CompletionKind::RESOURCE_PATHS: {
- if (bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
+ if (bool(EDITOR_GET("text_editor/completion/complete_file_paths"))) {
_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), suggestions);
}
} break;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index 1c98dfcdf6..f1b46e293b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -489,25 +489,37 @@ namespace Godot.Collections
ICollection<T>,
IEnumerable<T>
{
+ private static godot_variant ToVariantFunc(in Array<T> godotArray) =>
+ VariantUtils.CreateFromArray(godotArray);
+
+ private static Array<T> FromVariantFunc(in godot_variant variant) =>
+ VariantUtils.ConvertToArrayObject<T>(variant);
+
// ReSharper disable StaticMemberInGenericType
// Warning is about unique static fields being created for each generic type combination:
// https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html
// In our case this is exactly what we want.
- private static unsafe delegate* managed<in T, godot_variant> _convertToVariantCallback;
- private static unsafe delegate* managed<in godot_variant, T> _convertToManagedCallback;
+ private static readonly unsafe delegate* managed<in T, godot_variant> ConvertToVariantCallback;
+ private static readonly unsafe delegate* managed<in godot_variant, T> ConvertToManagedCallback;
// ReSharper restore StaticMemberInGenericType
static unsafe Array()
{
- _convertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>();
- _convertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>();
+ VariantConversionCallbacks.GenericConversionCallbacks[typeof(Array<T>)] =
+ (
+ (IntPtr)(delegate* managed<in Array<T>, godot_variant>)&ToVariantFunc,
+ (IntPtr)(delegate* managed<in godot_variant, Array<T>>)&FromVariantFunc
+ );
+
+ ConvertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>();
+ ConvertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>();
}
private static unsafe void ValidateVariantConversionCallbacks()
{
- if (_convertToVariantCallback == null || _convertToManagedCallback == null)
+ if (ConvertToVariantCallback == null || ConvertToManagedCallback == null)
{
throw new InvalidOperationException(
$"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'.");
@@ -653,7 +665,7 @@ namespace Godot.Collections
get
{
_underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem);
- return _convertToManagedCallback(borrowElem);
+ return ConvertToManagedCallback(borrowElem);
}
set
{
@@ -663,7 +675,7 @@ namespace Godot.Collections
godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self);
godot_variant* itemPtr = &ptrw[index];
(*itemPtr).Dispose();
- *itemPtr = _convertToVariantCallback(value);
+ *itemPtr = ConvertToVariantCallback(value);
}
}
@@ -675,7 +687,7 @@ namespace Godot.Collections
/// <returns>The index of the item, or -1 if not found.</returns>
public unsafe int IndexOf(T item)
{
- using var variantValue = _convertToVariantCallback(item);
+ using var variantValue = ConvertToVariantCallback(item);
var self = (godot_array)_underlyingArray.NativeValue;
return NativeFuncs.godotsharp_array_index_of(ref self, variantValue);
}
@@ -693,7 +705,7 @@ namespace Godot.Collections
if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException(nameof(index));
- using var variantValue = _convertToVariantCallback(item);
+ using var variantValue = ConvertToVariantCallback(item);
var self = (godot_array)_underlyingArray.NativeValue;
NativeFuncs.godotsharp_array_insert(ref self, index, variantValue);
}
@@ -726,7 +738,7 @@ namespace Godot.Collections
/// <returns>The new size after adding the item.</returns>
public unsafe void Add(T item)
{
- using var variantValue = _convertToVariantCallback(item);
+ using var variantValue = ConvertToVariantCallback(item);
var self = (godot_array)_underlyingArray.NativeValue;
_ = NativeFuncs.godotsharp_array_add(ref self, variantValue);
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
index ae44f8f4ba..354212da1b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
@@ -22,8 +22,7 @@ namespace Godot.Bridge
}
bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method),
- new NativeVariantPtrArgs(args),
- argCount, out godot_variant retValue);
+ new NativeVariantPtrArgs(args, argCount), out godot_variant retValue);
if (!methodInvoked)
{
@@ -102,7 +101,7 @@ namespace Godot.Bridge
return godot_bool.False;
}
- *outRet = Marshaling.ConvertManagedObjectToVariant(ret);
+ *outRet = ret.CopyNativeVariant();
return godot_bool.True;
}
catch (Exception e)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index 57240624bc..44ea8fc83d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -9,7 +9,7 @@ namespace Godot.Bridge
{
// @formatter:off
public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback;
- public delegate* unmanaged<IntPtr, godot_variant**, uint, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
+ public delegate* unmanaged<IntPtr, void*, godot_variant**, int, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals;
public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle;
public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 092724a6b1..d83cf43eb2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -339,7 +339,7 @@ namespace Godot.Bridge
*outOwnerIsNull = godot_bool.False;
owner.RaiseGodotClassSignalCallbacks(CustomUnsafe.AsRef(eventSignalName),
- new NativeVariantPtrArgs(args), argCount);
+ new NativeVariantPtrArgs(args, argCount));
}
catch (Exception e)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
index bdedd2e87a..f9309ca13e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
@@ -26,11 +26,12 @@ namespace Godot
/// }
/// </code>
/// </example>
- public readonly struct Callable
+ public readonly partial struct Callable
{
private readonly Object _target;
private readonly StringName _method;
private readonly Delegate _delegate;
+ private readonly unsafe delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> _trampoline;
/// <summary>
/// Object that contains the method.
@@ -48,10 +49,10 @@ namespace Godot
public Delegate Delegate => _delegate;
/// <summary>
- /// Converts a <see cref="Delegate"/> to a <see cref="Callable"/>.
+ /// Trampoline function pointer for dynamically invoking <see cref="Callable.Delegate"/>.
/// </summary>
- /// <param name="delegate">The delegate to convert.</param>
- public static implicit operator Callable(Delegate @delegate) => new Callable(@delegate);
+ public unsafe delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> Trampoline
+ => _trampoline;
/// <summary>
/// Constructs a new <see cref="Callable"/> for the method called <paramref name="method"/>
@@ -59,22 +60,21 @@ namespace Godot
/// </summary>
/// <param name="target">Object that contains the method.</param>
/// <param name="method">Name of the method that will be called.</param>
- public Callable(Object target, StringName method)
+ public unsafe Callable(Object target, StringName method)
{
_target = target;
_method = method;
_delegate = null;
+ _trampoline = null;
}
- /// <summary>
- /// Constructs a new <see cref="Callable"/> for the given <paramref name="delegate"/>.
- /// </summary>
- /// <param name="delegate">Delegate method that will be called.</param>
- public Callable(Delegate @delegate)
+ private unsafe Callable(Delegate @delegate,
+ delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> trampoline)
{
_target = @delegate?.Target as Object;
_method = null;
_delegate = @delegate;
+ _trampoline = trampoline;
}
private const int VarArgsSpanThreshold = 5;
@@ -149,5 +149,59 @@ namespace Godot
NativeFuncs.godotsharp_callable_call_deferred(callable, (godot_variant**)argsPtr, argc);
}
}
+
+ /// <summary>
+ /// <para>
+ /// Constructs a new <see cref="Callable"/> using the <paramref name="trampoline"/>
+ /// function pointer to dynamically invoke the given <paramref name="delegate"/>.
+ /// </para>
+ /// <para>
+ /// The parameters passed to the <paramref name="trampoline"/> function are:
+ /// </para>
+ /// <list type="number">
+ /// <item>
+ /// <term>delegateObj</term>
+ /// <description>The given <paramref name="delegate"/>, upcast to <see cref="object"/>.</description>
+ /// </item>
+ /// <item>
+ /// <term>args</term>
+ /// <description>Array of <see cref="godot_variant"/> arguments.</description>
+ /// </item>
+ /// <item>
+ /// <term>ret</term>
+ /// <description>Return value of type <see cref="godot_variant"/>.</description>
+ /// </item>
+ ///</list>
+ /// <para>
+ /// The delegate should be downcast to a more specific delegate type before invoking.
+ /// </para>
+ /// </summary>
+ /// <example>
+ /// Usage example:
+ ///
+ /// <code>
+ /// static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ /// {
+ /// if (args.Count != 1)
+ /// throw new ArgumentException($&quot;Callable expected {1} arguments but received {args.Count}.&quot;);
+ ///
+ /// TResult res = ((Func&lt;int, string&gt;)delegateObj)(
+ /// VariantConversionCallbacks.GetToManagedCallback&lt;int&gt;()(args[0])
+ /// );
+ ///
+ /// ret = VariantConversionCallbacks.GetToVariantCallback&lt;string&gt;()(res);
+ /// }
+ ///
+ /// var callable = Callable.CreateWithUnsafeTrampoline((int num) =&gt; &quot;foo&quot; + num.ToString(), &amp;Trampoline);
+ /// var res = (string)callable.Call(10);
+ /// Console.WriteLine(res);
+ /// </code>
+ /// </example>
+ /// <param name="delegate">Delegate method that will be called.</param>
+ /// <param name="trampoline">Trampoline function pointer for invoking the delegate.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe Callable CreateWithUnsafeTrampoline(Delegate @delegate,
+ delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> trampoline)
+ => new(@delegate, trampoline);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs
new file mode 100644
index 0000000000..6c6a104019
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs
@@ -0,0 +1,480 @@
+using System;
+using System.Runtime.CompilerServices;
+using Godot.NativeInterop;
+
+namespace Godot;
+
+#nullable enable
+
+public readonly partial struct Callable
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void ThrowIfArgCountMismatch(NativeVariantPtrArgs args, int countExpected,
+ [CallerArgumentExpression("args")] string? paramName = null)
+ {
+ if (countExpected != args.Count)
+ ThrowArgCountMismatch(countExpected, args.Count, paramName);
+
+ static void ThrowArgCountMismatch(int countExpected, int countReceived, string? paramName)
+ {
+ throw new ArgumentException(
+ "Invalid argument count for invoking callable." +
+ $" Expected {countExpected} arguments, received {countReceived}.",
+ paramName);
+ }
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="Callable"/> for the given <paramref name="action"/>.
+ /// </summary>
+ /// <param name="action">Action method that will be called.</param>
+ public static unsafe Callable From(
+ Action action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 0);
+
+ ((Action)delegateObj)();
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0>(
+ Action<T0> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 1);
+
+ ((Action<T0>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0, T1>(
+ Action<T0, T1> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 2);
+
+ ((Action<T0, T1>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0, T1, T2>(
+ Action<T0, T1, T2> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 3);
+
+ ((Action<T0, T1, T2>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0, T1, T2, T3>(
+ Action<T0, T1, T2, T3> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 4);
+
+ ((Action<T0, T1, T2, T3>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4>(
+ Action<T0, T1, T2, T3, T4> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 5);
+
+ ((Action<T0, T1, T2, T3, T4>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, T5>(
+ Action<T0, T1, T2, T3, T4, T5> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 6);
+
+ ((Action<T0, T1, T2, T3, T4, T5>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+ VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6>(
+ Action<T0, T1, T2, T3, T4, T5, T6> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 7);
+
+ ((Action<T0, T1, T2, T3, T4, T5, T6>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+ VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+ VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7>(
+ Action<T0, T1, T2, T3, T4, T5, T6, T7> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 8);
+
+ ((Action<T0, T1, T2, T3, T4, T5, T6, T7>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+ VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+ VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
+ VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From(Action)"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, T8>(
+ Action<T0, T1, T2, T3, T4, T5, T6, T7, T8> action
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 9);
+
+ ((Action<T0, T1, T2, T3, T4, T5, T6, T7, T8>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+ VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+ VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
+ VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]),
+ VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8])
+ );
+
+ ret = default;
+ }
+
+ return CreateWithUnsafeTrampoline(action, &Trampoline);
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="Callable"/> for the given <paramref name="func"/>.
+ /// </summary>
+ /// <param name="func">Action method that will be called.</param>
+ public static unsafe Callable From<TResult>(
+ Func<TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 0);
+
+ TResult res = ((Func<TResult>)delegateObj)();
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, TResult>(
+ Func<T0, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 1);
+
+ TResult res = ((Func<T0, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, T1, TResult>(
+ Func<T0, T1, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 2);
+
+ TResult res = ((Func<T0, T1, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, T1, T2, TResult>(
+ Func<T0, T1, T2, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 3);
+
+ TResult res = ((Func<T0, T1, T2, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, T1, T2, T3, TResult>(
+ Func<T0, T1, T2, T3, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 4);
+
+ TResult res = ((Func<T0, T1, T2, T3, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, TResult>(
+ Func<T0, T1, T2, T3, T4, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 5);
+
+ TResult res = ((Func<T0, T1, T2, T3, T4, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, T5, TResult>(
+ Func<T0, T1, T2, T3, T4, T5, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 6);
+
+ TResult res = ((Func<T0, T1, T2, T3, T4, T5, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+ VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, TResult>(
+ Func<T0, T1, T2, T3, T4, T5, T6, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 7);
+
+ TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+ VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+ VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, TResult>(
+ Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 8);
+
+ TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+ VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+ VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
+ VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+
+ /// <inheritdoc cref="From{TResult}(Func{TResult})"/>
+ public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>(
+ Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult> func
+ )
+ {
+ static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
+ {
+ ThrowIfArgCountMismatch(args, 9);
+
+ TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>)delegateObj)(
+ VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]),
+ VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]),
+ VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]),
+ VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]),
+ VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]),
+ VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]),
+ VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]),
+ VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]),
+ VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8])
+ );
+
+ ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res);
+ }
+
+ return CreateWithUnsafeTrampoline(func, &Trampoline);
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
index 9b3969d453..d19e0c08f2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
@@ -30,33 +30,23 @@ namespace Godot
}
[UnmanagedCallersOnly]
- internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, godot_variant** args, uint argc,
- godot_variant* outRet)
+ internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, void* trampoline,
+ godot_variant** args, int argc, godot_variant* outRet)
{
try
{
- // TODO: Optimize
- var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
- var managedArgs = new object?[argc];
-
- var parameterInfos = @delegate.Method.GetParameters();
- var paramsLength = parameterInfos.Length;
-
- if (argc != paramsLength)
+ if (trampoline == null)
{
- throw new InvalidOperationException(
- $"The delegate expects {paramsLength} arguments, but received {argc}.");
+ throw new ArgumentNullException(nameof(trampoline),
+ "Cannot dynamically invoke delegate because the trampoline is null.");
}
- for (uint i = 0; i < argc; i++)
- {
- managedArgs[i] = Marshaling.ConvertVariantToManagedObjectOfType(
- *args[i], parameterInfos[i].ParameterType);
- }
+ var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
+ var trampolineFn = (delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void>)trampoline;
- object? invokeRet = @delegate.DynamicInvoke(managedArgs);
+ trampolineFn(@delegate, new NativeVariantPtrArgs(args, argc), out godot_variant ret);
- *outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet);
+ *outRet = ret;
}
catch (Exception e)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 93103d0f6b..f8793332a0 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -356,35 +356,47 @@ namespace Godot.Collections
IDictionary<TKey, TValue>,
IReadOnlyDictionary<TKey, TValue>
{
+ private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) =>
+ VariantUtils.CreateFromDictionary(godotDictionary);
+
+ private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) =>
+ VariantUtils.ConvertToDictionaryObject<TKey, TValue>(variant);
+
// ReSharper disable StaticMemberInGenericType
// Warning is about unique static fields being created for each generic type combination:
// https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html
// In our case this is exactly what we want.
- private static unsafe delegate* managed<in TKey, godot_variant> _convertKeyToVariantCallback;
- private static unsafe delegate* managed<in godot_variant, TKey> _convertKeyToManagedCallback;
- private static unsafe delegate* managed<in TValue, godot_variant> _convertValueToVariantCallback;
- private static unsafe delegate* managed<in godot_variant, TValue> _convertValueToManagedCallback;
+ private static readonly unsafe delegate* managed<in TKey, godot_variant> ConvertKeyToVariantCallback;
+ private static readonly unsafe delegate* managed<in godot_variant, TKey> ConvertKeyToManagedCallback;
+ private static readonly unsafe delegate* managed<in TValue, godot_variant> ConvertValueToVariantCallback;
+ private static readonly unsafe delegate* managed<in godot_variant, TValue> ConvertValueToManagedCallback;
// ReSharper restore StaticMemberInGenericType
static unsafe Dictionary()
{
- _convertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>();
- _convertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>();
- _convertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>();
- _convertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>();
+ VariantConversionCallbacks.GenericConversionCallbacks[typeof(Dictionary<TKey, TValue>)] =
+ (
+ (IntPtr)(delegate* managed<in Dictionary<TKey, TValue>, godot_variant>)&ToVariantFunc,
+ (IntPtr)(delegate* managed<in godot_variant, Dictionary<TKey, TValue>>)&FromVariantFunc
+ );
+
+ ConvertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>();
+ ConvertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>();
+ ConvertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>();
+ ConvertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>();
}
private static unsafe void ValidateVariantConversionCallbacks()
{
- if (_convertKeyToVariantCallback == null || _convertKeyToManagedCallback == null)
+ if (ConvertKeyToVariantCallback == null || ConvertKeyToManagedCallback == null)
{
throw new InvalidOperationException(
$"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'.");
}
- if (_convertValueToVariantCallback == null || _convertValueToManagedCallback == null)
+ if (ConvertValueToVariantCallback == null || ConvertValueToManagedCallback == null)
{
throw new InvalidOperationException(
$"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'.");
@@ -473,14 +485,14 @@ namespace Godot.Collections
{
get
{
- using var variantKey = _convertKeyToVariantCallback(key);
+ using var variantKey = ConvertKeyToVariantCallback(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant value).ToBool())
{
using (value)
- return _convertValueToManagedCallback(value);
+ return ConvertValueToManagedCallback(value);
}
else
{
@@ -489,8 +501,8 @@ namespace Godot.Collections
}
set
{
- using var variantKey = _convertKeyToVariantCallback(key);
- using var variantValue = _convertValueToVariantCallback(value);
+ using var variantKey = ConvertKeyToVariantCallback(key);
+ using var variantValue = ConvertValueToVariantCallback(value);
var self = (godot_dictionary)_underlyingDict.NativeValue;
NativeFuncs.godotsharp_dictionary_set_value(ref self,
variantKey, variantValue);
@@ -539,8 +551,8 @@ namespace Godot.Collections
using (value)
{
return new KeyValuePair<TKey, TValue>(
- _convertKeyToManagedCallback(key),
- _convertValueToManagedCallback(value));
+ ConvertKeyToManagedCallback(key),
+ ConvertValueToManagedCallback(value));
}
}
@@ -552,13 +564,13 @@ namespace Godot.Collections
/// <param name="value">The object to add.</param>
public unsafe void Add(TKey key, TValue value)
{
- using var variantKey = _convertKeyToVariantCallback(key);
+ using var variantKey = ConvertKeyToVariantCallback(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool())
throw new ArgumentException("An element with the same key already exists.", nameof(key));
- using var variantValue = _convertValueToVariantCallback(value);
+ using var variantValue = ConvertValueToVariantCallback(value);
NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue);
}
@@ -569,7 +581,7 @@ namespace Godot.Collections
/// <returns>Whether or not this dictionary contains the given key.</returns>
public unsafe bool ContainsKey(TKey key)
{
- using var variantKey = _convertKeyToVariantCallback(key);
+ using var variantKey = ConvertKeyToVariantCallback(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool();
}
@@ -580,7 +592,7 @@ namespace Godot.Collections
/// <param name="key">The key of the element to remove.</param>
public unsafe bool Remove(TKey key)
{
- using var variantKey = _convertKeyToVariantCallback(key);
+ using var variantKey = ConvertKeyToVariantCallback(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool();
}
@@ -593,13 +605,13 @@ namespace Godot.Collections
/// <returns>If an object was found for the given <paramref name="key"/>.</returns>
public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
- using var variantKey = _convertKeyToVariantCallback(key);
+ using var variantKey = ConvertKeyToVariantCallback(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
using (retValue)
- value = found ? _convertValueToManagedCallback(retValue) : default;
+ value = found ? ConvertValueToManagedCallback(retValue) : default;
return found;
}
@@ -625,7 +637,7 @@ namespace Godot.Collections
unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
- using var variantKey = _convertKeyToVariantCallback(item.Key);
+ using var variantKey = ConvertKeyToVariantCallback(item.Key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
@@ -635,7 +647,7 @@ namespace Godot.Collections
if (!found)
return false;
- using var variantValue = _convertValueToVariantCallback(item.Value);
+ using var variantValue = ConvertValueToVariantCallback(item.Value);
return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool();
}
}
@@ -670,7 +682,7 @@ namespace Godot.Collections
unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
- using var variantKey = _convertKeyToVariantCallback(item.Key);
+ using var variantKey = ConvertKeyToVariantCallback(item.Key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
@@ -680,7 +692,7 @@ namespace Godot.Collections
if (!found)
return false;
- using var variantValue = _convertValueToVariantCallback(item.Value);
+ using var variantValue = ConvertValueToVariantCallback(item.Value);
if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool())
{
return NativeFuncs.godotsharp_dictionary_remove_key(
@@ -717,6 +729,7 @@ namespace Godot.Collections
public static implicit operator Variant(Dictionary<TKey, TValue> from) => Variant.CreateFrom(from);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static explicit operator Dictionary<TKey, TValue>(Variant from) => from.AsGodotDictionary<TKey, TValue>();
+ public static explicit operator Dictionary<TKey, TValue>(Variant from) =>
+ from.AsGodotDictionary<TKey, TValue>();
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index 76b186cd15..649661ee06 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -721,10 +721,19 @@ namespace Godot.NativeInterop
if (p_managed_callable.Delegate != null)
{
var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate);
- IntPtr objectPtr = p_managed_callable.Target != null ? Object.GetPtr(p_managed_callable.Target) : IntPtr.Zero;
- NativeFuncs.godotsharp_callable_new_with_delegate(
- GCHandle.ToIntPtr(gcHandle), objectPtr, out godot_callable callable);
- return callable;
+
+ IntPtr objectPtr = p_managed_callable.Target != null ?
+ Object.GetPtr(p_managed_callable.Target) :
+ IntPtr.Zero;
+
+ unsafe
+ {
+ NativeFuncs.godotsharp_callable_new_with_delegate(
+ GCHandle.ToIntPtr(gcHandle), (IntPtr)p_managed_callable.Trampoline,
+ objectPtr, out godot_callable callable);
+
+ return callable;
+ }
}
else
{
@@ -748,19 +757,22 @@ namespace Godot.NativeInterop
public static Callable ConvertCallableToManaged(in godot_callable p_callable)
{
if (NativeFuncs.godotsharp_callable_get_data_for_marshalling(p_callable,
- out IntPtr delegateGCHandle, out IntPtr godotObject,
- out godot_string_name name).ToBool())
+ out IntPtr delegateGCHandle, out IntPtr trampoline,
+ out IntPtr godotObject, out godot_string_name name).ToBool())
{
if (delegateGCHandle != IntPtr.Zero)
{
- return new Callable((Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target);
- }
- else
- {
- return new Callable(
- InteropUtils.UnmanagedGetManaged(godotObject),
- StringName.CreateTakingOwnershipOfDisposableValue(name));
+ unsafe
+ {
+ return Callable.CreateWithUnsafeTrampoline(
+ (Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target,
+ (delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void>)trampoline);
+ }
}
+
+ return new Callable(
+ InteropUtils.UnmanagedGetManaged(godotObject),
+ StringName.CreateTakingOwnershipOfDisposableValue(name));
}
// Some other unsupported callable
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index 20ede9f0dd..088f4e7ecf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -141,11 +141,11 @@ namespace Godot.NativeInterop
public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest,
in godot_string p_element);
- public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_object,
- out godot_callable r_callable);
+ public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_trampoline,
+ IntPtr p_object, out godot_callable r_callable);
internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(in godot_callable p_callable,
- out IntPtr r_delegate_handle, out IntPtr r_object, out godot_string_name r_name);
+ out IntPtr r_delegate_handle, out IntPtr r_trampoline, out IntPtr r_object, out godot_string_name r_name);
internal static partial godot_variant godotsharp_callable_call(in godot_callable p_callable,
godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs
index 422df74c23..d8c5d99cb8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs
@@ -8,8 +8,22 @@ namespace Godot.NativeInterop
public unsafe ref struct NativeVariantPtrArgs
{
private godot_variant** _args;
+ private int _argc;
- internal NativeVariantPtrArgs(godot_variant** args) => _args = args;
+ internal NativeVariantPtrArgs(godot_variant** args, int argc)
+ {
+ _args = args;
+ _argc = argc;
+ }
+
+ /// <summary>
+ /// Returns the number of arguments.
+ /// </summary>
+ public int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _argc;
+ }
public ref godot_variant this[int index]
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs
index 9cde62c7c5..4b3db0c01a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs
@@ -1,10 +1,15 @@
using System;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
namespace Godot.NativeInterop;
+// TODO: Change VariantConversionCallbacks<T>. Store the callback in a static field for quick repeated access, instead of checking every time.
internal static unsafe class VariantConversionCallbacks
{
+ internal static System.Collections.Generic.Dictionary<Type, (IntPtr ToVariant, IntPtr FromVariant)>
+ GenericConversionCallbacks = new();
+
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
internal static delegate*<in T, godot_variant> GetToVariantCallback<T>()
{
@@ -502,6 +507,26 @@ internal static unsafe class VariantConversionCallbacks
&FromVariant;
}
+ // TODO:
+ // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode.
+ // We could make the Godot collections implement an interface and use IsAssignableFrom instead.
+ // Or we could just skip the check and always look for a conversion callback for the type.
+ if (typeOfT.IsGenericType)
+ {
+ var genericTypeDef = typeOfT.GetGenericTypeDefinition();
+
+ if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) ||
+ genericTypeDef == typeof(Godot.Collections.Array<>))
+ {
+ RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle);
+
+ if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion))
+ {
+ return (delegate*<in T, godot_variant>)genericConversion.ToVariant;
+ }
+ }
+ }
+
return null;
}
@@ -1005,6 +1030,26 @@ internal static unsafe class VariantConversionCallbacks
&ToVariant;
}
+ // TODO:
+ // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode.
+ // We could make the Godot collections implement an interface and use IsAssignableFrom instead.
+ // Or we could just skip the check and always look for a conversion callback for the type.
+ if (typeOfT.IsGenericType)
+ {
+ var genericTypeDef = typeOfT.GetGenericTypeDefinition();
+
+ if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) ||
+ genericTypeDef == typeof(Godot.Collections.Array<>))
+ {
+ RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle);
+
+ if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion))
+ {
+ return (delegate*<in godot_variant, T>)genericConversion.FromVariant;
+ }
+ }
+ }
+
// ReSharper restore RedundantCast
return null;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
index 5cb678c280..60ee6eb6f4 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
@@ -202,7 +202,7 @@ namespace Godot
// ReSharper disable once VirtualMemberNeverOverridden.Global
protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal,
- NativeVariantPtrArgs args, int argCount)
+ NativeVariantPtrArgs args)
{
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index a63b668387..e3fb254f49 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -52,6 +52,7 @@
<Compile Include="Core\AABB.cs" />
<Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
<Compile Include="Core\Bridge\MethodInfo.cs" />
+ <Compile Include="Core\Callable.generics.cs" />
<Compile Include="Core\CustomGCHandle.cs" />
<Compile Include="Core\Array.cs" />
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index 2717b945f6..e20a88076a 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -447,15 +447,16 @@ void godotsharp_packed_string_array_add(PackedStringArray *r_dest, const String
r_dest->append(*p_element);
}
-void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, const Object *p_object, Callable *r_callable) {
+void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, void *p_trampoline,
+ const Object *p_object, Callable *r_callable) {
// TODO: Use pooling for ManagedCallable instances.
ObjectID objid = p_object ? p_object->get_instance_id() : ObjectID();
- CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, objid));
+ CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, p_trampoline, objid));
memnew_placement(r_callable, Callable(managed_callable));
}
bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable,
- GCHandleIntPtr *r_delegate_handle, Object **r_object, StringName *r_name) {
+ GCHandleIntPtr *r_delegate_handle, void **r_trampoline, Object **r_object, StringName *r_name) {
if (p_callable->is_custom()) {
CallableCustom *custom = p_callable->get_custom();
CallableCustom::CompareEqualFunc compare_equal_func = custom->get_compare_equal_func();
@@ -463,18 +464,21 @@ bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable,
if (compare_equal_func == ManagedCallable::compare_equal_func_ptr) {
ManagedCallable *managed_callable = static_cast<ManagedCallable *>(custom);
*r_delegate_handle = managed_callable->get_delegate();
+ *r_trampoline = managed_callable->get_trampoline();
*r_object = nullptr;
memnew_placement(r_name, StringName());
return true;
} else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) {
SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom);
*r_delegate_handle = { nullptr };
+ *r_trampoline = nullptr;
*r_object = ObjectDB::get_instance(signal_awaiter_callable->get_object());
memnew_placement(r_name, StringName(signal_awaiter_callable->get_signal()));
return true;
} else if (compare_equal_func == EventSignalCallable::compare_equal_func_ptr) {
EventSignalCallable *event_signal_callable = static_cast<EventSignalCallable *>(custom);
*r_delegate_handle = { nullptr };
+ *r_trampoline = nullptr;
*r_object = ObjectDB::get_instance(event_signal_callable->get_object());
memnew_placement(r_name, StringName(event_signal_callable->get_signal()));
return true;
@@ -482,11 +486,13 @@ bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable,
// Some other CallableCustom. We only support ManagedCallable.
*r_delegate_handle = { nullptr };
+ *r_trampoline = nullptr;
*r_object = nullptr;
memnew_placement(r_name, StringName());
return false;
} else {
*r_delegate_handle = { nullptr };
+ *r_trampoline = nullptr;
*r_object = ObjectDB::get_instance(p_callable->get_object_id());
memnew_placement(r_name, StringName(p_callable->get_method()));
return true;
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 185a7e60cf..d45bf4025f 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -137,7 +137,7 @@ private:
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 = GLOBAL_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 + "_" + arch);
if (!DirAccess::exists(data_dir_root)) {
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
index 0c2c533090..28edc41d98 100644
--- a/modules/mono/managed_callable.cpp
+++ b/modules/mono/managed_callable.cpp
@@ -92,7 +92,7 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
ERR_FAIL_COND(delegate_handle.value == nullptr);
GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs(
- delegate_handle, p_arguments, p_argcount, &r_return_value);
+ delegate_handle, trampoline, p_arguments, p_argcount, &r_return_value);
r_call_error.error = Callable::CallError::CALL_OK;
}
@@ -106,7 +106,8 @@ void ManagedCallable::release_delegate_handle() {
// Why you do this clang-format...
/* clang-format off */
-ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id) : delegate_handle(p_delegate_handle), object_id(p_object_id) {
+ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, void *p_trampoline, ObjectID p_object_id) :
+ delegate_handle(p_delegate_handle), trampoline(p_trampoline), object_id(p_object_id) {
#ifdef GD_MONO_HOT_RELOAD
{
MutexLock lock(instances_mutex);
diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h
index 26cd164fb6..b3a137dedf 100644
--- a/modules/mono/managed_callable.h
+++ b/modules/mono/managed_callable.h
@@ -40,6 +40,7 @@
class ManagedCallable : public CallableCustom {
friend class CSharpLanguage;
GCHandleIntPtr delegate_handle;
+ void *trampoline = nullptr;
ObjectID object_id;
#ifdef GD_MONO_HOT_RELOAD
@@ -58,6 +59,7 @@ public:
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
_FORCE_INLINE_ GCHandleIntPtr get_delegate() const { return delegate_handle; }
+ _FORCE_INLINE_ void *get_trampoline() const { return trampoline; }
static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
@@ -67,7 +69,7 @@ public:
void release_delegate_handle();
- ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id);
+ ManagedCallable(GCHandleIntPtr p_delegate_handle, void *p_trampoline, ObjectID p_object_id);
~ManagedCallable();
};
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 13b599fe55..9c26fa2b0a 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -74,7 +74,7 @@ struct ManagedCallbacks {
using Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, void *p_def_vals, int32_t p_count);
using FuncSignalAwaiter_SignalCallback = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, int32_t, bool *);
- using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *);
+ using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, void *, const Variant **, int32_t, const Variant *);
using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr);
using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *);
using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *);
diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
index 62bb396d15..a7b89f58ac 100644
--- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml
+++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
@@ -42,6 +42,10 @@
The root path to use for RPCs and replication. Instead of an absolute path, a relative path will be used to find the node upon which the RPC should be executed.
This effectively allows to have different branches of the scene tree to be managed by different MultiplayerAPI, allowing for example to run both client and server in the same scene.
</member>
+ <member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true">
+ Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server.
+ [b]Note:[/b] Support for this feature may depend on the current [MultiplayerPeer] configuration. See [method MultiplayerPeer.is_server_relay_supported].
+ </member>
</members>
<signals>
<signal name="peer_packet">
diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp
index 9755f426d5..95857392c7 100644
--- a/modules/multiplayer/multiplayer_synchronizer.cpp
+++ b/modules/multiplayer/multiplayer_synchronizer.cpp
@@ -118,6 +118,10 @@ void MultiplayerSynchronizer::set_net_id(uint32_t p_net_id) {
}
bool MultiplayerSynchronizer::update_outbound_sync_time(uint64_t p_msec) {
+ if (last_sync_msec == p_msec) {
+ // last_sync_msec has been updated on this frame.
+ return true;
+ }
if (p_msec >= last_sync_msec + interval_msec) {
last_sync_msec = p_msec;
return true;
diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp
index b3b642f815..7df9b95b30 100644
--- a/modules/multiplayer/scene_cache_interface.cpp
+++ b/modules/multiplayer/scene_cache_interface.cpp
@@ -105,8 +105,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
multiplayer_peer->set_transfer_channel(0);
multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
- multiplayer_peer->set_target_peer(p_from);
- multiplayer_peer->put_packet(packet.ptr(), packet.size());
+ multiplayer->send_command(p_from, packet.ptr(), packet.size());
}
void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) {
@@ -162,10 +161,9 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat
Error err = OK;
for (int peer_id : p_peers) {
- multiplayer_peer->set_target_peer(peer_id);
multiplayer_peer->set_transfer_channel(0);
multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
- err = multiplayer_peer->put_packet(packet.ptr(), packet.size());
+ err = multiplayer->send_command(peer_id, packet.ptr(), packet.size());
ERR_FAIL_COND_V(err != OK, err);
// Insert into confirmed, but as false since it was not confirmed.
psc->confirmed_peers.insert(peer_id, false);
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index 3fc1eef366..f94f8ef658 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -67,12 +67,20 @@ Error SceneMultiplayer::poll() {
const uint8_t *packet;
int len;
+ int channel = multiplayer_peer->get_packet_channel();
+ MultiplayerPeer::TransferMode mode = multiplayer_peer->get_packet_mode();
+
Error err = multiplayer_peer->get_packet(&packet, len);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error getting packet! %d", err));
- remote_sender_id = sender;
- _process_packet(sender, packet, len);
- remote_sender_id = 0;
+ if (len && (packet[0] & CMD_MASK) == NETWORK_COMMAND_SYS) {
+ // Sys messages are processed separately since they might call _process_packet themselves.
+ _process_sys(sender, packet, len, mode, channel);
+ } else {
+ remote_sender_id = sender;
+ _process_packet(sender, packet, len);
+ remote_sender_id = 0;
+ }
if (!multiplayer_peer.is_valid()) {
return OK; // It's also possible that a packet or RPC caused a disconnection, so also check here.
@@ -86,6 +94,7 @@ void SceneMultiplayer::clear() {
connected_peers.clear();
packet_cache.clear();
cache->clear();
+ relay_buffer->clear();
}
void SceneMultiplayer::set_root_path(const NodePath &p_path) {
@@ -166,10 +175,123 @@ void SceneMultiplayer::_process_packet(int p_from, const uint8_t *p_packet, int
case NETWORK_COMMAND_SYNC: {
replicator->on_sync_receive(p_from, p_packet, p_packet_len);
} break;
+ default: {
+ ERR_FAIL_MSG("Invalid network command from " + itos(p_from));
+ } break;
+ }
+}
+
+Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_packet_len) {
+ if (server_relay && get_unique_id() != 1 && p_to != 1 && multiplayer_peer->is_server_relay_supported()) {
+ // Send relay packet.
+ relay_buffer->seek(0);
+ relay_buffer->put_u8(NETWORK_COMMAND_SYS);
+ relay_buffer->put_u8(SYS_COMMAND_RELAY);
+ relay_buffer->put_32(p_to); // Set the destination.
+ relay_buffer->put_data(p_packet, p_packet_len);
+ multiplayer_peer->set_target_peer(1);
+ const Vector<uint8_t> data = relay_buffer->get_data_array();
+ return multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position());
+ }
+ if (p_to < 0) {
+ for (const int &pid : connected_peers) {
+ if (pid == -p_to) {
+ continue;
+ }
+ multiplayer_peer->set_target_peer(pid);
+ multiplayer_peer->put_packet(p_packet, p_packet_len);
+ }
+ return OK;
+ } else {
+ multiplayer_peer->set_target_peer(p_to);
+ return multiplayer_peer->put_packet(p_packet, p_packet_len);
+ }
+}
+
+void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_packet_len, MultiplayerPeer::TransferMode p_mode, int p_channel) {
+ ERR_FAIL_COND_MSG(p_packet_len < SYS_CMD_SIZE, "Invalid packet received. Size too small.");
+ uint8_t sys_cmd_type = p_packet[1];
+ int32_t peer = int32_t(decode_uint32(&p_packet[2]));
+ switch (sys_cmd_type) {
+ case SYS_COMMAND_ADD_PEER: {
+ ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1);
+ _add_peer(peer);
+ } break;
+ case SYS_COMMAND_DEL_PEER: {
+ ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1);
+ _del_peer(peer);
+ } break;
+ case SYS_COMMAND_RELAY: {
+ ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported());
+ ERR_FAIL_COND(p_packet_len < SYS_CMD_SIZE + 1);
+ const uint8_t *packet = p_packet + SYS_CMD_SIZE;
+ int len = p_packet_len - SYS_CMD_SIZE;
+ bool should_process = false;
+ if (get_unique_id() == 1) { // I am the server.
+ // Direct messages to server should not go through relay.
+ ERR_FAIL_COND(peer > 0 && !connected_peers.has(peer));
+ // Send relay packet.
+ relay_buffer->seek(0);
+ relay_buffer->put_u8(NETWORK_COMMAND_SYS);
+ relay_buffer->put_u8(SYS_COMMAND_RELAY);
+ relay_buffer->put_32(p_from); // Set the source.
+ relay_buffer->put_data(packet, len);
+ const Vector<uint8_t> data = relay_buffer->get_data_array();
+ multiplayer_peer->set_transfer_mode(p_mode);
+ multiplayer_peer->set_transfer_channel(p_channel);
+ if (peer > 0) {
+ multiplayer_peer->set_target_peer(peer);
+ multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position());
+ } else {
+ for (const int &P : connected_peers) {
+ // Not to sender, nor excluded.
+ if (P == p_from || (peer < 0 && P != -peer)) {
+ continue;
+ }
+ multiplayer_peer->set_target_peer(P);
+ multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position());
+ }
+ }
+ if (peer == 0 || peer == -1) {
+ should_process = true;
+ peer = p_from; // Process as the source.
+ }
+ } else {
+ ERR_FAIL_COND(p_from != 1); // Bug.
+ should_process = true;
+ }
+ if (should_process) {
+ remote_sender_id = peer;
+ _process_packet(peer, packet, len);
+ remote_sender_id = 0;
+ }
+ } break;
+ default: {
+ ERR_FAIL();
+ }
}
}
void SceneMultiplayer::_add_peer(int p_id) {
+ if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) {
+ // Notify others of connection, and send connected peers to newly connected one.
+ uint8_t buf[SYS_CMD_SIZE];
+ buf[0] = NETWORK_COMMAND_SYS;
+ buf[1] = SYS_COMMAND_ADD_PEER;
+ multiplayer_peer->set_transfer_channel(0);
+ multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
+ for (const int &P : connected_peers) {
+ // Send new peer to already connected.
+ encode_uint32(p_id, &buf[2]);
+ multiplayer_peer->set_target_peer(P);
+ multiplayer_peer->put_packet(buf, sizeof(buf));
+ // Send already connected to new peer.
+ encode_uint32(P, &buf[2]);
+ multiplayer_peer->set_target_peer(p_id);
+ multiplayer_peer->put_packet(buf, sizeof(buf));
+ }
+ }
+
connected_peers.insert(p_id);
cache->on_peer_change(p_id, true);
replicator->on_peer_change(p_id, true);
@@ -177,6 +299,23 @@ void SceneMultiplayer::_add_peer(int p_id) {
}
void SceneMultiplayer::_del_peer(int p_id) {
+ if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) {
+ // Notify others of disconnection.
+ uint8_t buf[SYS_CMD_SIZE];
+ buf[0] = NETWORK_COMMAND_SYS;
+ buf[1] = SYS_COMMAND_DEL_PEER;
+ multiplayer_peer->set_transfer_channel(0);
+ multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
+ encode_uint32(p_id, &buf[2]);
+ for (const int &P : connected_peers) {
+ if (P == p_id) {
+ continue;
+ }
+ multiplayer_peer->set_target_peer(P);
+ multiplayer_peer->put_packet(buf, sizeof(buf));
+ }
+ }
+
replicator->on_peer_change(p_id, false);
cache->on_peer_change(p_id, false);
connected_peers.erase(p_id);
@@ -209,11 +348,9 @@ Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer
packet_cache.write[0] = NETWORK_COMMAND_RAW;
memcpy(&packet_cache.write[1], &r[0], p_data.size());
- multiplayer_peer->set_target_peer(p_to);
multiplayer_peer->set_transfer_channel(p_channel);
multiplayer_peer->set_transfer_mode(p_mode);
-
- return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 1);
+ return send_command(p_to, packet_cache.ptr(), p_data.size() + 1);
}
void SceneMultiplayer::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) {
@@ -303,6 +440,15 @@ Error SceneMultiplayer::object_configuration_remove(Object *p_obj, Variant p_con
return ERR_INVALID_PARAMETER;
}
+void SceneMultiplayer::set_server_relay_enabled(bool p_enabled) {
+ ERR_FAIL_COND_MSG(multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_DISCONNECTED, "Cannot change the server relay option while the multiplayer peer is active.");
+ server_relay = p_enabled;
+}
+
+bool SceneMultiplayer::is_server_relay_enabled() const {
+ return server_relay;
+}
+
void SceneMultiplayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_path", "path"), &SceneMultiplayer::set_root_path);
ClassDB::bind_method(D_METHOD("get_root_path"), &SceneMultiplayer::get_root_path);
@@ -311,17 +457,22 @@ void SceneMultiplayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &SceneMultiplayer::is_refusing_new_connections);
ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &SceneMultiplayer::set_allow_object_decoding);
ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &SceneMultiplayer::is_object_decoding_allowed);
+ ClassDB::bind_method(D_METHOD("set_server_relay_enabled", "enabled"), &SceneMultiplayer::set_server_relay_enabled);
+ ClassDB::bind_method(D_METHOD("is_server_relay_enabled"), &SceneMultiplayer::is_server_relay_enabled);
ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &SceneMultiplayer::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled");
+
ADD_PROPERTY_DEFAULT("refuse_new_connections", false);
ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet")));
}
SceneMultiplayer::SceneMultiplayer() {
+ relay_buffer.instantiate();
replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this)));
rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this)));
cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this)));
diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h
index a99cca7b21..06d7b2c7a7 100644
--- a/modules/multiplayer/scene_multiplayer.h
+++ b/modules/multiplayer/scene_multiplayer.h
@@ -49,6 +49,17 @@ public:
NETWORK_COMMAND_SPAWN,
NETWORK_COMMAND_DESPAWN,
NETWORK_COMMAND_SYNC,
+ NETWORK_COMMAND_SYS,
+ };
+
+ enum SysCommands {
+ SYS_COMMAND_ADD_PEER,
+ SYS_COMMAND_DEL_PEER,
+ SYS_COMMAND_RELAY,
+ };
+
+ enum {
+ SYS_CMD_SIZE = 6, // Command + sys command + peer_id (+ optional payload).
};
// For each command, the 4 MSB can contain custom flags, as defined by subsystems.
@@ -74,6 +85,8 @@ private:
NodePath root_path;
bool allow_object_decoding = false;
+ bool server_relay = true;
+ Ref<StreamPeerBuffer> relay_buffer;
Ref<SceneCacheInterface> cache;
Ref<SceneReplicationInterface> replicator;
@@ -84,6 +97,7 @@ protected:
void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len);
void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len);
+ void _process_sys(int p_from, const uint8_t *p_packet, int p_packet_len, MultiplayerPeer::TransferMode p_mode, int p_channel);
void _add_peer(int p_id);
void _del_peer(int p_id);
@@ -111,6 +125,7 @@ public:
void set_root_path(const NodePath &p_path);
NodePath get_root_path() const;
+ Error send_command(int p_to, const uint8_t *p_packet, int p_packet_len); // Used internally to relay packets when needed.
Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0);
String get_rpc_md5(const Object *p_obj);
@@ -123,6 +138,9 @@ public:
void set_allow_object_decoding(bool p_enable);
bool is_object_decoding_allowed() const;
+ void set_server_relay_enabled(bool p_enabled);
+ bool is_server_relay_enabled() const;
+
Ref<SceneCacheInterface> get_path_cache() { return cache; }
#ifdef DEBUG_ENABLED
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index 8359580805..659ce7316a 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -72,7 +72,7 @@ void SceneReplicationInterface::_free_remotes(const PeerInfo &p_info) {
for (const KeyValue<uint32_t, ObjectID> &E : p_info.recv_nodes) {
Node *node = tracked_nodes.has(E.value) ? get_id_as<Node>(E.value) : nullptr;
ERR_CONTINUE(!node);
- node->queue_delete();
+ node->queue_free();
}
}
@@ -370,10 +370,9 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size,
#endif
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
- peer->set_target_peer(p_peer);
peer->set_transfer_channel(0);
peer->set_transfer_mode(p_reliable ? MultiplayerPeer::TRANSFER_MODE_RELIABLE : MultiplayerPeer::TRANSFER_MODE_UNRELIABLE);
- return peer->put_packet(p_buffer, p_size);
+ return multiplayer->send_command(p_peer, p_buffer, p_size);
}
Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len) {
@@ -582,7 +581,7 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p
if (node->get_parent() != nullptr) {
node->get_parent()->remove_child(node);
}
- node->queue_delete();
+ node->queue_free();
spawner->emit_signal(SNAME("despawned"), node);
return OK;
diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp
index 65090b9316..acc113c901 100644
--- a/modules/multiplayer/scene_rpc_interface.cpp
+++ b/modules/multiplayer/scene_rpc_interface.cpp
@@ -412,8 +412,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
if (has_all_peers) {
// They all have verified paths, so send fast.
- peer->set_target_peer(p_to); // To all of you.
- peer->put_packet(packet_cache.ptr(), ofs); // A message with love.
+ multiplayer->send_command(p_to, packet_cache.ptr(), ofs);
} else {
// Unreachable because the node ID is never compressed if the peers doesn't know it.
CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32);
@@ -438,16 +437,14 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P);
- peer->set_target_peer(P); // To this one specifically.
-
if (confirmed) {
// This one confirmed path, so use id.
encode_uint32(psc_id, &(packet_cache.write[1]));
- peer->put_packet(packet_cache.ptr(), ofs);
+ multiplayer->send_command(P, packet_cache.ptr(), ofs);
} else {
// This one did not confirm path yet, so use entire path (sorry!).
encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag.
- peer->put_packet(packet_cache.ptr(), ofs + path_len);
+ multiplayer->send_command(P, packet_cache.ptr(), ofs + path_len);
}
}
}
diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index 185e44c29d..123f860ce9 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -224,7 +224,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our interaction profiles
Ref<OpenXRInteractionProfile> profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/khr/simple_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -236,7 +236,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our Vive controller profile
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -257,7 +257,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our WMR controller profile
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/microsoft/motion_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -280,7 +280,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our Meta touch controller profile
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/oculus/touch_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -305,7 +305,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our Valve index controller profile
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/valve/index_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -333,7 +333,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our HP MR controller profile
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/hp/mixed_reality_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -356,7 +356,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our Samsung Odyssey controller profile,
// Note that this controller is only identified specifically on WMR, on SteamVR this is identified as a normal WMR controller.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/samsung/odyssey_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -379,7 +379,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our Vive Cosmos controller
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_cosmos_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -403,7 +403,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Note, Vive Focus 3 currently is not yet supported as a stand alone device
// however HTC currently has a beta OpenXR runtime in testing we may support in the near future
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_focus3_controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
@@ -427,7 +427,7 @@ void OpenXRActionMap::create_default_action_sets() {
// Create our Huawei controller
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/huawei/controller");
- profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp
index 51c402d746..b5223e5903 100644
--- a/modules/openxr/editor/openxr_action_map_editor.cpp
+++ b/modules/openxr/editor/openxr_action_map_editor.cpp
@@ -128,7 +128,7 @@ void OpenXRActionMapEditor::_update_interaction_profiles() {
interaction_profiles.remove_at(0);
tabs->remove_child(interaction_profile);
- interaction_profile->queue_delete();
+ interaction_profile->queue_free();
}
// in with the new...
@@ -205,7 +205,7 @@ void OpenXRActionMapEditor::_on_remove_action_set(Object *p_action_set_editor) {
action_map->remove_action_set(action_set);
actionsets_vb->remove_child(action_set_editor);
- action_set_editor->queue_delete();
+ action_set_editor->queue_free();
}
void OpenXRActionMapEditor::_on_action_removed() {
@@ -290,7 +290,7 @@ void OpenXRActionMapEditor::_on_tab_button_pressed(int p_tab) {
action_map->remove_interaction_profile(interaction_profile);
tabs->remove_child(profile_editor);
- profile_editor->queue_delete();
+ profile_editor->queue_free();
}
void OpenXRActionMapEditor::open_action_map(String p_path) {
@@ -364,7 +364,7 @@ OpenXRActionMapEditor::OpenXRActionMapEditor() {
select_interaction_profile_dialog->connect("interaction_profile_selected", callable_mp(this, &OpenXRActionMapEditor::_on_interaction_profile_selected));
add_child(select_interaction_profile_dialog);
- _load_action_map(ProjectSettings::get_singleton()->get("xr/openxr/default_action_map"));
+ _load_action_map(GLOBAL_GET("xr/openxr/default_action_map"));
}
OpenXRActionMapEditor::~OpenXRActionMapEditor() {
diff --git a/modules/openxr/editor/openxr_action_set_editor.cpp b/modules/openxr/editor/openxr_action_set_editor.cpp
index 804808a6b9..3869146e8e 100644
--- a/modules/openxr/editor/openxr_action_set_editor.cpp
+++ b/modules/openxr/editor/openxr_action_set_editor.cpp
@@ -140,7 +140,7 @@ void OpenXRActionSetEditor::_on_remove_action(Object *p_action_editor) {
// And remove it....
action_map->remove_action(action->get_name_with_set()); // remove it from the set and any interaction profile it relates to
actions_vb->remove_child(action_editor);
- action_editor->queue_delete();
+ action_editor->queue_free();
// Let action map editor know so we can update our interaction profiles
emit_signal("action_removed");
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index 56404f796c..2abfc93722 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -4,7 +4,7 @@
Class for searching text for patterns using regular expressions.
</brief_description>
<description>
- A regular expression (or regex) is a compact language that can be used to recognise strings that follow a specific pattern, such as URLs, email addresses, complete sentences, etc. For instance, a regex of [code]ab[0-9][/code] would find any string that is [code]ab[/code] followed by any number from [code]0[/code] to [code]9[/code]. For a more in-depth look, you can easily find various tutorials and detailed explanations on the Internet.
+ A regular expression (or regex) is a compact language that can be used to recognise strings that follow a specific pattern, such as URLs, email addresses, complete sentences, etc. For example, a regex of [code]ab[0-9][/code] would find any string that is [code]ab[/code] followed by any number from [code]0[/code] to [code]9[/code]. For a more in-depth look, you can easily find various tutorials and detailed explanations on the Internet.
To begin, the RegEx object needs to be compiled with the search pattern using [method compile] before it can be used.
[codeblock]
var regex = RegEx.new()
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index 2261342467..b2e38a1dad 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -131,9 +131,14 @@ if env["builtin_harfbuzz"]:
env_harfbuzz.Append(
CCFLAGS=[
"-DHAVE_FREETYPE",
- "-DHAVE_GRAPHITE2",
]
)
+ if env["graphite"]:
+ env_harfbuzz.Append(
+ CCFLAGS=[
+ "-DHAVE_GRAPHITE2",
+ ]
+ )
if env["builtin_freetype"]:
env_harfbuzz.Prepend(CPPPATH=["#thirdparty/freetype/include"])
if env["builtin_graphite"] and env["graphite"]:
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 0929d3a2b0..c0fec4531d 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -2613,7 +2613,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -2630,9 +2630,10 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
ea.x = fd->embolden * double(size.x) / 64.0;
}
+ double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
- } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ } else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
return (gl[p_glyph | mod].advance + ea).round();
} else {
return gl[p_glyph | mod].advance + ea;
@@ -2665,7 +2666,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -2710,7 +2711,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -2755,7 +2756,7 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -2795,7 +2796,7 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -2835,7 +2836,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -2881,7 +2882,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -3208,7 +3209,7 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
if (!fd->msdf && fd->cache[size]->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -3261,13 +3262,15 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range);
} else {
+ double scale = _font_get_scale(p_font_rid, p_size);
Point2 cpos = p_pos;
- cpos.y = Math::floor(cpos.y);
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- cpos.x = ((int)Math::floor(cpos.x + 0.125));
+ cpos.x = cpos.x + 0.125;
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- cpos.x = ((int)Math::floor(cpos.x + 0.25));
- } else {
+ cpos.x = cpos.x + 0.25;
+ }
+ if (scale == 1.0) {
+ cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
cpos += gl.rect.position;
@@ -3298,7 +3301,7 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
if (!fd->msdf && fd->cache[size]->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -3352,12 +3355,14 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size * 2, fd->msdf_range);
} else {
Point2 cpos = p_pos;
- cpos.y = Math::floor(cpos.y);
+ double scale = _font_get_scale(p_font_rid, p_size);
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- cpos.x = ((int)Math::floor(cpos.x + 0.125));
+ cpos.x = cpos.x + 0.125;
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- cpos.x = ((int)Math::floor(cpos.x + 0.25));
- } else {
+ cpos.x = cpos.x + 0.25;
+ }
+ if (scale == 1.0) {
+ cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
cpos += gl.rect.position;
@@ -4975,7 +4980,8 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap
Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, const RID &p_font, int64_t p_font_size) {
hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size);
- bool subpos = (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
+ double scale = _font_get_scale(p_font, p_font_size);
+ bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
ERR_FAIL_COND_V(hb_font == nullptr, Glyph());
hb_buffer_clear_contents(p_sd->hb_buffer);
@@ -5001,25 +5007,24 @@ Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char
gl.font_size = p_font_size;
if (glyph_count > 0) {
- double scale = _font_get_scale(p_font, p_font_size);
if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
if (subpos) {
- gl.advance = glyph_pos[0].x_advance / (64.0 / scale) + _get_extra_advance(p_font, p_font_size);
+ gl.advance = (double)glyph_pos[0].x_advance / (64.0 / scale) + _get_extra_advance(p_font, p_font_size);
} else {
- gl.advance = Math::round(glyph_pos[0].x_advance / (64.0 / scale) + _get_extra_advance(p_font, p_font_size));
+ gl.advance = Math::round((double)glyph_pos[0].x_advance / (64.0 / scale) + _get_extra_advance(p_font, p_font_size));
}
} else {
- gl.advance = -Math::round(glyph_pos[0].y_advance / (64.0 / scale));
+ gl.advance = -Math::round((double)glyph_pos[0].y_advance / (64.0 / scale));
}
gl.count = 1;
gl.index = glyph_info[0].codepoint;
if (subpos) {
- gl.x_off = glyph_pos[0].x_offset / (64.0 / scale);
+ gl.x_off = (double)glyph_pos[0].x_offset / (64.0 / scale);
} else {
- gl.x_off = Math::round(glyph_pos[0].x_offset / (64.0 / scale));
+ gl.x_off = Math::round((double)glyph_pos[0].x_offset / (64.0 / scale));
}
- gl.y_off = -Math::round(glyph_pos[0].y_offset / (64.0 / scale));
+ gl.y_off = -Math::round((double)glyph_pos[0].y_offset / (64.0 / scale));
if ((glyph_info[0].codepoint != 0) || !u_isgraph(p_char)) {
gl.flags |= GRAPHEME_IS_VALID;
@@ -5092,7 +5097,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
double sp_gl = p_sd->extra_spacing[SPACING_GLYPH];
bool last_run = (p_sd->end == p_end);
double ea = _get_extra_advance(f, fs);
- bool subpos = (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_AUTO && fs <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
+ bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_AUTO && fs <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
ERR_FAIL_COND(hb_font == nullptr);
hb_buffer_clear_contents(p_sd->hb_buffer);
@@ -5131,7 +5136,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -5193,19 +5198,19 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
_ensure_glyph(fd, fss, gl.index | mod);
if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
if (subpos) {
- gl.advance = glyph_pos[i].x_advance / (64.0 / scale) + ea;
+ gl.advance = (double)glyph_pos[i].x_advance / (64.0 / scale) + ea;
} else {
- gl.advance = Math::round(glyph_pos[i].x_advance / (64.0 / scale) + ea);
+ gl.advance = Math::round((double)glyph_pos[i].x_advance / (64.0 / scale) + ea);
}
} else {
- gl.advance = -Math::round(glyph_pos[i].y_advance / (64.0 / scale));
+ gl.advance = -Math::round((double)glyph_pos[i].y_advance / (64.0 / scale));
}
if (subpos) {
- gl.x_off = glyph_pos[i].x_offset / (64.0 / scale);
+ gl.x_off = (double)glyph_pos[i].x_offset / (64.0 / scale);
} else {
- gl.x_off = Math::round(glyph_pos[i].x_offset / (64.0 / scale));
+ gl.x_off = Math::round((double)glyph_pos[i].x_offset / (64.0 / scale));
}
- gl.y_off = -Math::round(glyph_pos[i].y_offset / (64.0 / scale));
+ gl.y_off = -Math::round((double)glyph_pos[i].y_offset / (64.0 / scale));
}
if (!last_run || i < glyph_count - 1) {
// Do not add extra spacing to the last glyph of the string.
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 4a46e17868..cd8df34f60 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -1694,7 +1694,7 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -1711,9 +1711,10 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
ea.x = fd->embolden * double(size.x) / 64.0;
}
+ double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
- } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ } else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
return (gl[p_glyph | mod].advance + ea).round();
} else {
return gl[p_glyph | mod].advance + ea;
@@ -1746,7 +1747,7 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -1791,7 +1792,7 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -1836,7 +1837,7 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -1876,7 +1877,7 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -1916,7 +1917,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -1962,7 +1963,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -2271,7 +2272,7 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
if (!fd->msdf && fd->cache[size]->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -2325,12 +2326,14 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range);
} else {
Point2 cpos = p_pos;
- cpos.y = Math::floor(cpos.y);
+ double scale = _font_get_scale(p_font_rid, p_size);
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- cpos.x = ((int)Math::floor(cpos.x + 0.125));
+ cpos.x = cpos.x + 0.125;
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- cpos.x = ((int)Math::floor(cpos.x + 0.25));
- } else {
+ cpos.x = cpos.x + 0.25;
+ }
+ if (scale == 1.0) {
+ cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
cpos += gl.rect.position;
@@ -2361,7 +2364,7 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
if (!fd->msdf && fd->cache[size]->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)ProjectSettings::get_singleton()->get("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -2415,12 +2418,14 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size * 2, fd->msdf_range);
} else {
Point2 cpos = p_pos;
- cpos.y = Math::floor(cpos.y);
+ double scale = _font_get_scale(p_font_rid, p_size);
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- cpos.x = ((int)Math::floor(cpos.x + 0.125));
+ cpos.x = cpos.x + 0.125;
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- cpos.x = ((int)Math::floor(cpos.x + 0.25));
- } else {
+ cpos.x = cpos.x + 0.25;
+ }
+ if (scale == 1.0) {
+ cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
cpos += gl.rect.position;
@@ -3643,17 +3648,18 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
}
}
+ double scale = _font_get_scale(gl.font_rid, gl.font_size);
if (gl.font_rid.is_valid()) {
- bool subpos = (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_AUTO && gl.font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
+ bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_AUTO && gl.font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
if (sd->text[j - sd->start] != 0 && !is_linebreak(sd->text[j - sd->start])) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
- gl.advance = Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x);
+ gl.advance = _font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x;
gl.x_off = 0;
gl.y_off = 0;
sd->ascent = MAX(sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size));
sd->descent = MAX(sd->descent, _font_get_descent(gl.font_rid, gl.font_size));
} else {
- gl.advance = Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).y);
+ gl.advance = _font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).y;
gl.x_off = -Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5);
gl.y_off = _font_get_ascent(gl.font_rid, gl.font_size);
sd->ascent = MAX(sd->ascent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index 69fb079970..83833d8253 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -549,7 +549,7 @@ void VideoStreamPlaybackTheora::play() {
}
playing = true;
- delay_compensation = ProjectSettings::get_singleton()->get("audio/video/video_delay_compensation_ms");
+ delay_compensation = GLOBAL_GET("audio/video/video_delay_compensation_ms");
delay_compensation /= 1000.0;
}
diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml
index 4888dca822..d4054948f6 100644
--- a/modules/upnp/doc_classes/UPNP.xml
+++ b/modules/upnp/doc_classes/UPNP.xml
@@ -41,7 +41,7 @@
func _ready():
thread = Thread.new()
- thread.start(self, "_upnp_setup", SERVER_PORT)
+ thread.start(_upnp_setup.bind(SERVER_PORT))
func _exit_tree():
# Wait for thread finish here to handle game exit while the thread is running.
diff --git a/modules/webp/resource_saver_webp.cpp b/modules/webp/resource_saver_webp.cpp
index bd71c2869a..56ff8097b4 100644
--- a/modules/webp/resource_saver_webp.cpp
+++ b/modules/webp/resource_saver_webp.cpp
@@ -38,8 +38,8 @@
Error ResourceSaverWebP::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
Ref<ImageTexture> texture = p_resource;
- ERR_FAIL_COND_V_MSG(!texture.is_valid(), ERR_INVALID_PARAMETER, "Can't save invalid texture as WEBP.");
- ERR_FAIL_COND_V_MSG(!texture->get_width(), ERR_INVALID_PARAMETER, "Can't save empty texture as WEBP.");
+ ERR_FAIL_COND_V_MSG(!texture.is_valid(), ERR_INVALID_PARAMETER, "Can't save invalid texture as WebP.");
+ ERR_FAIL_COND_V_MSG(!texture->get_width(), ERR_INVALID_PARAMETER, "Can't save empty texture as WebP.");
Ref<Image> img = texture->get_image();
@@ -52,7 +52,7 @@ Error ResourceSaverWebP::save_image(const String &p_path, const Ref<Image> &p_im
Vector<uint8_t> buffer = save_image_to_buffer(p_img, p_lossy, p_quality);
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save WEBP at path: '%s'.", p_path));
+ ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save WebP at path: '%s'.", p_path));
const uint8_t *reader = buffer.ptr();
diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp
index 049c1c3a32..af98788420 100644
--- a/modules/webp/webp_common.cpp
+++ b/modules/webp/webp_common.cpp
@@ -73,7 +73,7 @@ Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality) {
Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image) {
ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>());
- int compression_level = ProjectSettings::get_singleton()->get("rendering/textures/lossless_compression/webp_compression_level");
+ int compression_level = GLOBAL_GET("rendering/textures/lossless_compression/webp_compression_level");
compression_level = CLAMP(compression_level, 0, 9);
Ref<Image> img = p_image->duplicate();
@@ -139,7 +139,7 @@ Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer) {
ERR_FAIL_COND_V(r[0] != 'R' || r[1] != 'I' || r[2] != 'F' || r[3] != 'F' || r[8] != 'W' || r[9] != 'E' || r[10] != 'B' || r[11] != 'P', Ref<Image>());
WebPBitstreamFeatures features;
if (WebPGetFeatures(r, size, &features) != VP8_STATUS_OK) {
- ERR_FAIL_V_MSG(Ref<Image>(), "Error unpacking WEBP image.");
+ ERR_FAIL_V_MSG(Ref<Image>(), "Error unpacking WebP image.");
}
Vector<uint8_t> dst_image;
diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
index 927888fe21..0b42c6ed35 100644
--- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
+++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
@@ -6,7 +6,7 @@
<description>
This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.multiplayer_peer].
You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections.
- [signal MultiplayerPeer.connection_succeeded] and [signal MultiplayerPeer.server_disconnected] will not be emitted unless [code]server_compatibility[/code] is [code]true[/code] in [method initialize]. Beside that data transfer works like in a [MultiplayerPeer].
+ [signal MultiplayerPeer.connection_succeeded] and [signal MultiplayerPeer.server_disconnected] will not be emitted unless the peer is created using [method create_client]. Beside that data transfer works like in a [MultiplayerPeer].
[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>
@@ -28,6 +28,31 @@
Close all the add peer connections and channels, freeing all resources.
</description>
</method>
+ <method name="create_client">
+ <return type="int" enum="Error" />
+ <param index="0" name="peer_id" type="int" />
+ <param index="1" name="channels_config" type="Array" default="[]" />
+ <description>
+ Initialize the multiplayer peer as a client with the given [code]peer_id[/code] (must be between 2 and 2147483647). In this mode, you should only call [method add_peer] once and with [code]peer_id[/code] of [code]1[/code]. This mode enables [method MultiplayerPeer.is_server_relay_supported], allowing the upper [MultiplayerAPI] layer to perform peer exchange and packet relaying.
+ You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel).
+ </description>
+ </method>
+ <method name="create_mesh">
+ <return type="int" enum="Error" />
+ <param index="0" name="peer_id" type="int" />
+ <param index="1" name="channels_config" type="Array" default="[]" />
+ <description>
+ Initialize the multiplayer peer as a mesh (i.e. all peers connect to each other) with the given [code]peer_id[/code] (must be between 1 and 2147483647).
+ </description>
+ </method>
+ <method name="create_server">
+ <return type="int" enum="Error" />
+ <param index="0" name="channels_config" type="Array" default="[]" />
+ <description>
+ Initialize the multiplayer peer as a server (with unique ID of [code]1[/code]). This mode enables [method MultiplayerPeer.is_server_relay_supported], allowing the upper [MultiplayerAPI] layer to perform peer exchange and packet relaying.
+ You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel).
+ </description>
+ </method>
<method name="get_peer">
<return type="Dictionary" />
<param index="0" name="peer_id" type="int" />
@@ -48,18 +73,6 @@
Returns [code]true[/code] if the given [code]peer_id[/code] is in the peers map (it might not be connected though).
</description>
</method>
- <method name="initialize">
- <return type="int" enum="Error" />
- <param index="0" name="peer_id" type="int" />
- <param index="1" name="server_compatibility" type="bool" default="false" />
- <param index="2" name="channels_config" type="Array" default="[]" />
- <description>
- Initialize the multiplayer peer with the given [code]peer_id[/code] (must be between 1 and 2147483647).
- If [code]server_compatibilty[/code] is [code]false[/code] (default), the multiplayer peer will be immediately in state [constant MultiplayerPeer.CONNECTION_CONNECTED] and [signal MultiplayerPeer.connection_succeeded] will not be emitted.
- If [code]server_compatibilty[/code] is [code]true[/code] the peer will suppress all [signal MultiplayerPeer.peer_connected] signals until a peer with id [constant MultiplayerPeer.TARGET_PEER_SERVER] connects and then emit [signal MultiplayerPeer.connection_succeeded]. After that the signal [signal MultiplayerPeer.peer_connected] will be emitted for every already connected peer, and any new peer that might connect. If the server peer disconnects after that, signal [signal MultiplayerPeer.server_disconnected] will be emitted and state will become [constant MultiplayerPeer.CONNECTION_CONNECTED].
- You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel).
- </description>
- </method>
<method name="remove_peer">
<return type="void" />
<param index="0" name="peer_id" type="int" />
diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp
index e03b6b2473..5ea81d5a1b 100644
--- a/modules/webrtc/webrtc_multiplayer_peer.cpp
+++ b/modules/webrtc/webrtc_multiplayer_peer.cpp
@@ -34,7 +34,9 @@
#include "core/os/os.h"
void WebRTCMultiplayerPeer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility", "channels_config"), &WebRTCMultiplayerPeer::initialize, DEFVAL(false), DEFVAL(Array()));
+ ClassDB::bind_method(D_METHOD("create_server", "channels_config"), &WebRTCMultiplayerPeer::create_server, DEFVAL(Array()));
+ ClassDB::bind_method(D_METHOD("create_client", "peer_id", "channels_config"), &WebRTCMultiplayerPeer::create_client, DEFVAL(Array()));
+ ClassDB::bind_method(D_METHOD("create_mesh", "peer_id", "channels_config"), &WebRTCMultiplayerPeer::create_mesh, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("add_peer", "peer", "peer_id", "unreliable_lifetime"), &WebRTCMultiplayerPeer::add_peer, DEFVAL(1));
ClassDB::bind_method(D_METHOD("remove_peer", "peer_id"), &WebRTCMultiplayerPeer::remove_peer);
ClassDB::bind_method(D_METHOD("has_peer", "peer_id"), &WebRTCMultiplayerPeer::has_peer);
@@ -52,6 +54,15 @@ int WebRTCMultiplayerPeer::get_packet_peer() const {
return next_packet_peer;
}
+int WebRTCMultiplayerPeer::get_packet_channel() const {
+ return next_packet_channel < CH_RESERVED_MAX ? 0 : next_packet_channel - CH_RESERVED_MAX + 1;
+}
+
+MultiplayerPeer::TransferMode WebRTCMultiplayerPeer::get_packet_mode() const {
+ ERR_FAIL_INDEX_V(next_packet_channel, channels_modes.size(), TRANSFER_MODE_RELIABLE);
+ return channels_modes[next_packet_channel];
+}
+
bool WebRTCMultiplayerPeer::is_server() const {
return unique_id == TARGET_PEER_SERVER;
}
@@ -113,24 +124,14 @@ void WebRTCMultiplayerPeer::poll() {
// Signal newly connected peers
for (int &E : add) {
// Already connected to server: simply notify new peer.
- // NOTE: Mesh is always connected.
- if (connection_status == CONNECTION_CONNECTED) {
- emit_signal(SNAME("peer_connected"), E);
- }
-
- // Server emulation mode suppresses peer_conencted until server connects.
- if (server_compat && E == TARGET_PEER_SERVER) {
+ if (network_mode == MODE_CLIENT) {
+ ERR_CONTINUE(E != TARGET_PEER_SERVER); // Bug.
// Server connected.
connection_status = CONNECTION_CONNECTED;
emit_signal(SNAME("peer_connected"), TARGET_PEER_SERVER);
emit_signal(SNAME("connection_succeeded"));
- // Notify of all previously connected peers
- for (const KeyValue<int, Ref<ConnectedPeer>> &F : peer_map) {
- if (F.key != 1 && F.value->connected) {
- emit_signal(SNAME("peer_connected"), F.key);
- }
- }
- break; // Because we already notified of all newly added peers.
+ } else {
+ emit_signal(SNAME("peer_connected"), E);
}
}
// Fetch next packet
@@ -150,11 +151,14 @@ void WebRTCMultiplayerPeer::_find_next_peer() {
++E;
continue;
}
+ int idx = 0;
for (const Ref<WebRTCDataChannel> &F : E->value->channels) {
if (F->get_available_packet_count()) {
+ next_packet_channel = idx;
next_packet_peer = E->key;
return;
}
+ idx++;
}
++E;
}
@@ -165,11 +169,14 @@ void WebRTCMultiplayerPeer::_find_next_peer() {
++E;
continue;
}
+ int idx = 0;
for (const Ref<WebRTCDataChannel> &F : E->value->channels) {
if (F->get_available_packet_count()) {
+ next_packet_channel = idx;
next_packet_peer = E->key;
return;
}
+ idx++;
}
if (E->key == (int)next_packet_peer) {
break;
@@ -177,6 +184,7 @@ void WebRTCMultiplayerPeer::_find_next_peer() {
++E;
}
// No packet found
+ next_packet_channel = 0;
next_packet_peer = 0;
}
@@ -184,11 +192,28 @@ MultiplayerPeer::ConnectionStatus WebRTCMultiplayerPeer::get_connection_status()
return connection_status;
}
-Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Array p_channels_config) {
+Error WebRTCMultiplayerPeer::create_server(Array p_channels_config) {
+ return _initialize(1, MODE_SERVER, p_channels_config);
+}
+
+Error WebRTCMultiplayerPeer::create_client(int p_self_id, Array p_channels_config) {
+ ERR_FAIL_COND_V_MSG(p_self_id == 1, ERR_INVALID_PARAMETER, "Clients cannot have ID 1.");
+ return _initialize(p_self_id, MODE_CLIENT, p_channels_config);
+}
+
+Error WebRTCMultiplayerPeer::create_mesh(int p_self_id, Array p_channels_config) {
+ return _initialize(p_self_id, MODE_MESH, p_channels_config);
+}
+
+Error WebRTCMultiplayerPeer::_initialize(int p_self_id, NetworkMode p_mode, Array p_channels_config) {
ERR_FAIL_COND_V(p_self_id < 1 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER);
channels_config.clear();
+ channels_modes.clear();
+ channels_modes.push_back(TRANSFER_MODE_RELIABLE);
+ channels_modes.push_back(TRANSFER_MODE_UNRELIABLE_ORDERED);
+ channels_modes.push_back(TRANSFER_MODE_UNRELIABLE);
for (int i = 0; i < p_channels_config.size(); i++) {
- ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.Multiplayer::TransferMode'");
+ ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'");
int mode = p_channels_config[i].operator int();
// Initialize data channel configurations.
Dictionary cfg;
@@ -207,16 +232,17 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr
case TRANSFER_MODE_RELIABLE:
break;
default:
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.Multiplayer::TransferMode'. Got: %d", mode));
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'. Got: %d", mode));
}
channels_config.push_back(cfg);
+ channels_modes.push_back((TransferMode)mode);
}
unique_id = p_self_id;
- server_compat = p_server_compat;
+ network_mode = p_mode;
// Mesh and server are always connected
- if (!server_compat || p_self_id == 1) {
+ if (p_mode != MODE_CLIENT) {
connection_status = CONNECTION_CONNECTED;
} else {
connection_status = CONNECTION_CONNECTING;
@@ -224,6 +250,10 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr
return OK;
}
+bool WebRTCMultiplayerPeer::is_server_relay_supported() const {
+ return network_mode == MODE_SERVER || network_mode == MODE_CLIENT;
+}
+
int WebRTCMultiplayerPeer::get_unique_id() const {
ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, 1);
return unique_id;
@@ -261,7 +291,10 @@ Dictionary WebRTCMultiplayerPeer::get_peers() {
}
Error WebRTCMultiplayerPeer::add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime) {
- ERR_FAIL_COND_V(p_peer_id < 0 || p_peer_id > ~(1 << 31), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(network_mode == MODE_NONE, ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(network_mode == MODE_CLIENT && p_peer_id != 1, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(network_mode == MODE_SERVER && p_peer_id == 1, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_peer_id < 1 || p_peer_id > ~(1 << 31), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(p_unreliable_lifetime < 0, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(is_refusing_new_connections(), ERR_UNAUTHORIZED);
// Peer must be valid, and in new state (to create data channels)
@@ -308,8 +341,12 @@ void WebRTCMultiplayerPeer::remove_peer(int p_peer_id) {
if (peer->connected) {
peer->connected = false;
emit_signal(SNAME("peer_disconnected"), p_peer_id);
- if (server_compat && p_peer_id == TARGET_PEER_SERVER) {
- emit_signal(SNAME("server_disconnected"));
+ if (network_mode == MODE_CLIENT && p_peer_id == TARGET_PEER_SERVER) {
+ if (connection_status == CONNECTION_CONNECTING) {
+ emit_signal(SNAME("connection_failed"));
+ } else {
+ emit_signal(SNAME("server_disconnected"));
+ }
connection_status = CONNECTION_DISCONNECTED;
}
}
@@ -403,7 +440,9 @@ void WebRTCMultiplayerPeer::close() {
channels_config.clear();
unique_id = 0;
next_packet_peer = 0;
+ next_packet_channel = 0;
target_peer = 0;
+ network_mode = MODE_NONE;
connection_status = CONNECTION_DISCONNECTED;
}
diff --git a/modules/webrtc/webrtc_multiplayer_peer.h b/modules/webrtc/webrtc_multiplayer_peer.h
index ea7c60036b..3f608200fd 100644
--- a/modules/webrtc/webrtc_multiplayer_peer.h
+++ b/modules/webrtc/webrtc_multiplayer_peer.h
@@ -48,6 +48,13 @@ private:
CH_RESERVED_MAX = 3
};
+ enum NetworkMode {
+ MODE_NONE,
+ MODE_SERVER,
+ MODE_CLIENT,
+ MODE_MESH,
+ };
+
class ConnectedPeer : public RefCounted {
public:
Ref<WebRTCPeerConnection> connection;
@@ -67,19 +74,25 @@ private:
int client_count = 0;
ConnectionStatus connection_status = CONNECTION_DISCONNECTED;
int next_packet_peer = 0;
- bool server_compat = false;
+ int next_packet_channel = 0;
+ NetworkMode network_mode = MODE_NONE;
HashMap<int, Ref<ConnectedPeer>> peer_map;
+ List<TransferMode> channels_modes;
List<Dictionary> channels_config;
void _peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict);
void _find_next_peer();
+ Ref<ConnectedPeer> _get_next_peer();
+ Error _initialize(int p_self_id, NetworkMode p_mode, Array p_channels_config = Array());
public:
WebRTCMultiplayerPeer() {}
~WebRTCMultiplayerPeer();
- Error initialize(int p_self_id, bool p_server_compat = false, Array p_channels_config = Array());
+ Error create_server(Array p_channels_config = Array());
+ Error create_client(int p_self_id, Array p_channels_config = Array());
+ Error create_mesh(int p_self_id, Array p_channels_config = Array());
Error add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime = 1);
void remove_peer(int p_peer_id);
bool has_peer(int p_peer_id);
@@ -98,8 +111,11 @@ public:
int get_unique_id() const override;
int get_packet_peer() const override;
+ int get_packet_channel() const override;
+ TransferMode get_packet_mode() const override;
bool is_server() const override;
+ bool is_server_relay_supported() const override;
void poll() override;
diff --git a/modules/websocket/editor/editor_debugger_server_websocket.cpp b/modules/websocket/editor/editor_debugger_server_websocket.cpp
index 48bfbaa14e..a9c44141aa 100644
--- a/modules/websocket/editor/editor_debugger_server_websocket.cpp
+++ b/modules/websocket/editor/editor_debugger_server_websocket.cpp
@@ -71,8 +71,8 @@ String EditorDebuggerServerWebSocket::get_uri() const {
Error EditorDebuggerServerWebSocket::start(const String &p_uri) {
// Default host and port
- String bind_host = (String)EditorSettings::get_singleton()->get("network/debug/remote_host");
- int bind_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
+ String bind_host = (String)EDITOR_GET("network/debug/remote_host");
+ int bind_port = (int)EDITOR_GET("network/debug/remote_port");
// Optionally override
if (!p_uri.is_empty() && p_uri != "ws://") {
diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp
index c314ebd049..0202c5bac7 100644
--- a/modules/websocket/websocket_multiplayer_peer.cpp
+++ b/modules/websocket/websocket_multiplayer_peer.cpp
@@ -142,12 +142,21 @@ Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buff
Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
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);
-
if (is_server()) {
- return _server_relay(1, target_peer, &(buffer.ptr()[0]), buffer.size());
+ if (target_peer > 0) {
+ ERR_FAIL_COND_V_MSG(!peers_map.has(target_peer), ERR_INVALID_PARAMETER, "Peer not found: " + itos(target_peer));
+ get_peer(target_peer)->put_packet(p_buffer, p_buffer_size);
+ } else {
+ for (KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
+ if (target_peer && -target_peer == E.key) {
+ continue; // Excluded.
+ }
+ E.value->put_packet(p_buffer, p_buffer_size);
+ }
+ }
+ return OK;
} else {
- return get_peer(1)->put_packet(&(buffer.ptr()[0]), buffer.size());
+ return get_peer(1)->put_packet(p_buffer, p_buffer_size);
}
}
@@ -219,8 +228,41 @@ void WebSocketMultiplayerPeer::_poll_client() {
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);
+ if (connection_status == CONNECTION_CONNECTING) {
+ if (peer->get_available_packet_count() > 0) {
+ const uint8_t *in_buffer;
+ int size = 0;
+ Error err = peer->get_packet(&in_buffer, size);
+ if (err != OK || size != 4) {
+ peer->close(); // Will cause connection error on next poll.
+ ERR_FAIL_MSG("Invalid ID received from server");
+ }
+ unique_id = *((int32_t *)in_buffer);
+ if (unique_id < 2) {
+ peer->close(); // Will cause connection error on next poll.
+ ERR_FAIL_MSG("Invalid ID received from server");
+ }
+ connection_status = CONNECTION_CONNECTED;
+ emit_signal("peer_connected", 1);
+ emit_signal("connection_succeeded");
+ } else {
+ return; // Still waiting for an ID.
+ }
+ }
+ int pkts = peer->get_available_packet_count();
+ while (pkts > 0 && peer->get_ready_state() == WebSocketPeer::STATE_OPEN) {
+ const uint8_t *in_buffer;
+ int size = 0;
+ Error err = peer->get_packet(&in_buffer, size);
+ ERR_FAIL_COND(err != OK);
+ ERR_FAIL_COND(size <= 0);
+ Packet packet;
+ packet.data = (uint8_t *)memalloc(size);
+ memcpy(packet.data, in_buffer, size);
+ packet.size = size;
+ packet.source = 1;
+ incoming_packets.push_back(packet);
+ pkts--;
}
} else if (peer->get_ready_state() == WebSocketPeer::STATE_CLOSED) {
if (connection_status == CONNECTION_CONNECTED) {
@@ -278,9 +320,14 @@ void WebSocketMultiplayerPeer::_poll_server() {
// 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);
+ int32_t peer_id = id;
+ Error err = peer.ws->put_packet((const uint8_t *)&peer_id, sizeof(peer_id));
+ if (err == OK) {
+ peers_map[id] = peer.ws;
+ emit_signal("peer_connected", id);
+ } else {
+ ERR_PRINT("Failed to send ID to newly connected peer.");
+ }
continue;
} else if (state == WebSocketPeer::STATE_CONNECTING) {
continue; // Still connecting.
@@ -338,8 +385,19 @@ void WebSocketMultiplayerPeer::_poll_server() {
}
// Fetch packets
int pkts = ws->get_available_packet_count();
- while (pkts && ws->get_ready_state() == WebSocketPeer::STATE_OPEN) {
- _process_multiplayer(ws, id);
+ while (pkts > 0 && ws->get_ready_state() == WebSocketPeer::STATE_OPEN) {
+ const uint8_t *in_buffer;
+ int size = 0;
+ Error err = ws->get_packet(&in_buffer, size);
+ if (err != OK || size <= 0) {
+ break;
+ }
+ Packet packet;
+ packet.data = (uint8_t *)memalloc(size);
+ memcpy(packet.data, in_buffer, size);
+ packet.size = size;
+ packet.source = E.key;
+ incoming_packets.push_back(packet);
pkts--;
}
}
@@ -371,180 +429,6 @@ Ref<WebSocketPeer> WebSocketMultiplayerPeer::get_peer(int p_id) const {
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->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());
-}
-
-Vector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) {
- Vector<uint8_t> out;
- out.resize(PROTO_SIZE + p_data_size);
-
- uint8_t *w = out.ptrw();
- memcpy(&w[0], &p_type, 1);
- memcpy(&w[1], &p_from, 4);
- memcpy(&w[5], &p_to, 4);
- memcpy(&w[PROTO_SIZE], p_data, p_data_size);
-
- return out;
-}
-
-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(p_peer, SYS_ID, p_peer_id);
-
- // Then send the server peer (which will trigger connection_succeded in client)
- _send_sys(p_peer, SYS_ADD, 1);
-
- for (const KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
- ERR_CONTINUE(E.value.is_null());
-
- 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(E.value, SYS_ADD, p_peer_id);
- // Send others to new peer
- _send_sys(E.value, SYS_ADD, id);
- }
-}
-
-void WebSocketMultiplayerPeer::_send_del(int32_t p_peer_id) {
- for (const KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
- int32_t id = E.key;
- if (p_peer_id != id) {
- _send_sys(E.value, SYS_DEL, p_peer_id);
- }
- }
-}
-
-void WebSocketMultiplayerPeer::_store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size) {
- Packet packet;
- packet.data = (uint8_t *)memalloc(p_data_size);
- packet.size = p_data_size;
- packet.source = p_source;
- packet.destination = p_dest;
- memcpy(packet.data, &p_data[PROTO_SIZE], p_data_size);
- 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) {
- if (p_to == 1) {
- return OK; // Will not send to self
-
- } else if (p_to == 0) {
- for (KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
- if (E.key != p_from) {
- E.value->put_packet(p_buffer, p_buffer_size);
- }
- }
- return OK; // Sent to all but sender
-
- } else if (p_to < 0) {
- 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);
- }
- }
- return OK; // Sent to all but sender and excluded
-
- } else {
- ERR_FAIL_COND_V(p_to == p_from, FAILED);
-
- Ref<WebSocketPeer> peer_to = get_peer(p_to);
- ERR_FAIL_COND_V(peer_to.is_null(), FAILED);
-
- return peer_to->put_packet(p_buffer, p_buffer_size); // Sending to specific peer
- }
-}
-
-void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id) {
- ERR_FAIL_COND(!p_peer.is_valid());
-
- const uint8_t *in_buffer;
- int size = 0;
- int data_size = 0;
-
- Error err = p_peer->get_packet(&in_buffer, size);
-
- ERR_FAIL_COND(err != OK);
- ERR_FAIL_COND(size < PROTO_SIZE);
-
- data_size = size - PROTO_SIZE;
-
- uint8_t type = 0;
- uint32_t from = 0;
- int32_t to = 0;
- memcpy(&type, in_buffer, 1);
- memcpy(&from, &in_buffer[1], 4);
- memcpy(&to, &in_buffer[5], 4);
-
- if (is_server()) { // Server can resend
-
- 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
- _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 < -1) {
- // All but one, for us if not excluded
- _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
- _store_pkt(from, to, in_buffer, data_size);
- return;
- }
-
- // System message
- ERR_FAIL_COND(data_size < 4);
- int id = 0;
- memcpy(&id, &in_buffer[PROTO_SIZE], 4);
-
- switch (type) {
- case SYS_ADD: // Add peer
- 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"));
- }
- break;
-
- case SYS_DEL: // Remove peer
- emit_signal(SNAME("peer_disconnected"), id);
- peers_map.erase(id);
- break;
- case SYS_ID: // Hello, server assigned ID
- unique_id = id;
- break;
- default:
- ERR_FAIL_MSG("Invalid multiplayer message.");
- break;
- }
- }
-}
-
void WebSocketMultiplayerPeer::set_supported_protocols(const Vector<String> &p_protocols) {
peer_config->set_supported_protocols(p_protocols);
}
diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h
index 8e7b118faa..ebe013a7bf 100644
--- a/modules/websocket/websocket_multiplayer_peer.h
+++ b/modules/websocket/websocket_multiplayer_peer.h
@@ -42,9 +42,6 @@ class WebSocketMultiplayerPeer : public MultiplayerPeer {
GDCLASS(WebSocketMultiplayerPeer, MultiplayerPeer);
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:
@@ -59,7 +56,6 @@ protected:
struct Packet {
int source = 0;
- int destination = 0;
uint8_t *data = nullptr;
uint32_t size = 0;
};
@@ -90,20 +86,18 @@ protected:
static void _bind_methods();
- 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 */
- void set_target_peer(int p_target_peer) override;
- int get_packet_peer() const override;
- int get_unique_id() const override;
+ virtual void set_target_peer(int p_target_peer) override;
+ virtual int get_packet_peer() const override;
+ virtual int get_packet_channel() const override { return 0; }
+ virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; }
+ virtual int get_unique_id() const override;
+ virtual bool is_server_relay_supported() const override { return true; }
virtual int get_max_packet_size() const override;
virtual bool is_server() const override;
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index 49ff454f07..49dd9f7318 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -18,16 +18,16 @@
func _ready():
# We assume this node has a button as a child.
# This button is for the user to consent to entering immersive VR mode.
- $Button.connect("pressed", self, "_on_Button_pressed")
+ $Button.pressed.connect(self._on_Button_pressed)
webxr_interface = XRServer.find_interface("WebXR")
if webxr_interface:
# WebXR uses a lot of asynchronous callbacks, so we connect to various
# signals in order to receive them.
- webxr_interface.connect("session_supported", self, "_webxr_session_supported")
- webxr_interface.connect("session_started", self, "_webxr_session_started")
- webxr_interface.connect("session_ended", self, "_webxr_session_ended")
- webxr_interface.connect("session_failed", self, "_webxr_session_failed")
+ webxr_interface.session_supported.connect(self._webxr_session_supported)
+ webxr_interface.session_started.connect(self._webxr_session_started)
+ webxr_interface.session_ended.connect(self._webxr_session_ended)
+ webxr_interface.session_failed.connect(self._webxr_session_failed)
# This returns immediately - our _webxr_session_supported() method
# (which we connected to the "session_supported" signal above) will
diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h
index 34d068be3e..d8d5bd99cc 100644
--- a/modules/webxr/godot_webxr.h
+++ b/modules/webxr/godot_webxr.h
@@ -65,7 +65,7 @@ extern int godot_webxr_get_view_count();
extern int *godot_webxr_get_render_target_size();
extern float *godot_webxr_get_transform_for_eye(int p_eye);
extern float *godot_webxr_get_projection_for_eye(int p_eye);
-extern void godot_webxr_commit_for_eye(int p_eye, unsigned int p_destination_fbo);
+extern void godot_webxr_commit(unsigned int p_texture);
extern void godot_webxr_sample_controller_data();
extern int godot_webxr_get_controller_count();
diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js
index 9b75796ee5..c476a54c59 100644
--- a/modules/webxr/native/library_godot_webxr.js
+++ b/modules/webxr/native/library_godot_webxr.js
@@ -337,20 +337,18 @@ const GodotWebXR = {
return buf;
},
- godot_webxr_commit_for_eye__proxy: 'sync',
- godot_webxr_commit_for_eye__sig: 'vii',
- godot_webxr_commit_for_eye: function (p_eye, p_destination_fbo) {
+ godot_webxr_commit__proxy: 'sync',
+ godot_webxr_commit__sig: 'vi',
+ godot_webxr_commit: function (p_texture) {
if (!GodotWebXR.session || !GodotWebXR.pose) {
return;
}
- const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0;
const glLayer = GodotWebXR.session.renderState.baseLayer;
- const view = GodotWebXR.pose.views[view_index];
- const viewport = glLayer.getViewport(view);
+ const views = GodotWebXR.pose.views;
const gl = GodotWebXR.gl;
- const framebuffer = GL.framebuffers[p_destination_fbo];
+ const texture = GL.textures[p_texture];
const orig_framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
const orig_read_framebuffer = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
@@ -359,14 +357,27 @@ const GodotWebXR = {
// Copy from Godot render target into framebuffer from WebXR.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffer);
- gl.readBuffer(gl.COLOR_ATTACHMENT0);
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, glLayer.framebuffer);
-
- // Flip Y upside down on destination.
- gl.blitFramebuffer(0, 0, viewport.width, viewport.height,
- viewport.x, viewport.height, viewport.width, viewport.y,
- gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ for (let i = 0; i < views.length; i++) {
+ const viewport = glLayer.getViewport(views[i]);
+
+ const read_fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read_fbo);
+ if (views.length > 1) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, 0, i);
+ } else {
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
+ }
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, glLayer.framebuffer);
+
+ // Flip Y upside down on destination.
+ gl.blitFramebuffer(0, 0, viewport.width, viewport.height,
+ viewport.x, viewport.y + viewport.height, viewport.x + viewport.width, viewport.y,
+ gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.deleteFramebuffer(read_fbo);
+ }
// Restore state.
gl.bindFramebuffer(gl.FRAMEBUFFER, orig_framebuffer);
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index 6b671c1660..f6ed9f027e 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -415,8 +415,7 @@ Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, c
GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target);
- // @todo Support multiple eyes!
- godot_webxr_commit_for_eye(1, rt->fbo);
+ godot_webxr_commit(rt->color);
return blit_to_screen;
};