diff --git a/addons/netfox/channel-manager.gd b/addons/netfox/channel-manager.gd new file mode 100644 index 00000000..f8030513 --- /dev/null +++ b/addons/netfox/channel-manager.gd @@ -0,0 +1,40 @@ +extends Node +class_name ChannelManager + +var _available_channels: Array = [] +var _assigned_channels: Dictionary = {} +var _channel_limit: int +var _is_enabled: bool = false + +func _ready(): + _channel_limit = ProjectSettings.get_setting("netfox/channel_manager/channel_limit", 16) + _init_channels() + +func _init_channels() -> void: + _available_channels.resize(_channel_limit) + for i in _channel_limit: + _available_channels[i] = i + 1 # Channel 0 is default, start from 1 + +func is_enabled() -> bool: + return ProjectSettings.get_setting("netfox/channel_manager/enabled", false) + +func get_channel() -> int: + if _available_channels.is_empty(): + return -1 # No channels available + return _available_channels.pop_front() + +func free_channel(channel: int) -> void: + if channel in _assigned_channels: + _assigned_channels.erase(channel) + _available_channels.append(channel) + +func assign_channel(node: Node, channel: int) -> void: + _assigned_channels[channel] = node + +func has_channel(channel: int) -> bool: + return channel in _assigned_channels + +func set_channel_limit(limit: int) -> void: + _channel_limit = limit + _available_channels.clear() + _init_channels() diff --git a/addons/netfox/netfox.gd b/addons/netfox/netfox.gd index 0e95310a..d50ceb37 100644 --- a/addons/netfox/netfox.gd +++ b/addons/netfox/netfox.gd @@ -122,6 +122,19 @@ var SETTINGS = [ "name": "netfox/events/enabled", "value": true, "type": TYPE_BOOL + }, + # Channel Manager settings + { + "name": "netfox/channel_manager/enabled", + "value": true, + "type": TYPE_BOOL + }, + { + "name": "netfox/channel_manager/channel_limit", + "value": 50, + "type": TYPE_INT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "1,100,or_greater" } ] @@ -145,6 +158,10 @@ const AUTOLOADS = [ { "name": "NetworkPerformance", "path": ROOT + "/network-performance.gd" + }, + { + "name": "ChannelManager", + "path": ROOT + "/channel-manager.gd" } ] @@ -178,6 +195,7 @@ func _enter_tree(): for type in TYPES: add_custom_type(type.name, type.base, load(type.script), load(type.icon)) + func _exit_tree(): if ProjectSettings.get_setting("netfox/general/clear_settings", false): diff --git a/addons/netfox/network-time-synchronizer.gd b/addons/netfox/network-time-synchronizer.gd index bf0b77e3..8a89b65d 100644 --- a/addons/netfox/network-time-synchronizer.gd +++ b/addons/netfox/network-time-synchronizer.gd @@ -129,6 +129,52 @@ signal on_panic(offset: float) ## ## Starting multiple times has no effect. func start(): + if NetworkTime._channel_manager.is_enabled(): + rpc_config("_request_ping", { + "rpc_mode": 3, # MultiplayerAPI.RPC_MODE_ANY_PEER + "transfer_mode": 1, # MultiplayerPeer.TRANSFER_MODE_UNRELIABLE + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + rpc_config("_respond_ping", { + "rpc_mode": 3, # MultiplayerAPI.RPC_MODE_ANY_PEER + "transfer_mode": 1, # MultiplayerPeer.TRANSFER_MODE_UNRELIABLE + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + rpc_config("_request_timestamp", { + "rpc_mode": 3, # MultiplayerAPI.RPC_MODE_ANY_PEER + "transfer_mode": 2, # MultiplayerPeer.TRANSFER_MODE_RELIABLE + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + rpc_config("_set_timestamp", { + "rpc_mode": 3, # MultiplayerAPI.RPC_MODE_ANY_PEER + "transfer_mode": 2, # MultiplayerPeer.TRANSFER_MODE_RELIABLE + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + else: + rpc_config("_request_ping", { + "rpc_mode": 3, + "transfer_mode": 1, + "call_local": false, + }) + rpc_config("_respond_ping", { + "rpc_mode": 3, + "transfer_mode": 1, + "call_local": false, + }) + rpc_config("_request_timestamp", { + "rpc_mode": 3, # MultiplayerAPI.RPC_MODE_ANY_PEER + "transfer_mode": 2, # MultiplayerPeer.TRANSFER_MODE_RELIABLE + "call_local": false + }) + rpc_config("_set_timestamp", { + "rpc_mode": 3, # MultiplayerAPI.RPC_MODE_ANY_PEER + "transfer_mode": 2, # MultiplayerPeer.TRANSFER_MODE_RELIABLE + "call_local": false + }) if _active: return @@ -217,14 +263,12 @@ func _discipline_clock(): _offset = offset - nudge -@rpc("any_peer", "call_remote", "unreliable") func _send_ping(idx: int): var ping_received = _clock.get_time() var sender = multiplayer.get_remote_sender_id() _send_pong.rpc_id(sender, idx, ping_received, _clock.get_time()) -@rpc("any_peer", "call_remote", "unreliable") func _send_pong(idx: int, ping_received: float, pong_sent: float): var pong_received = _clock.get_time() @@ -246,12 +290,10 @@ func _send_pong(idx: int, ping_received: float, pong_sent: float): # Discipline clock based on new sample _discipline_clock() -@rpc("any_peer", "call_remote", "reliable") func _request_timestamp(): _logger.debug("Requested initial timestamp @ %.4fs raw time", [_clock.get_raw_time()]) _set_timestamp.rpc_id(multiplayer.get_remote_sender_id(), _clock.get_time()) -@rpc("any_peer", "call_remote", "reliable") func _set_timestamp(timestamp: float): _logger.debug("Received initial timestamp @ %.4fs raw time", [_clock.get_raw_time()]) _clock.set_time(timestamp) diff --git a/addons/netfox/network-time.gd b/addons/netfox/network-time.gd index 8a03dc87..1e759326 100644 --- a/addons/netfox/network-time.gd +++ b/addons/netfox/network-time.gd @@ -325,6 +325,13 @@ var clock_offset: float: var remote_clock_offset: float: get: return NetworkTimeSynchronizer.remote_offset + + +var _channel_manager: ChannelManager + + + + ## Emitted before a tick loop is run. signal before_tick_loop() @@ -492,6 +499,21 @@ func ticks_between(seconds_from: float, seconds_to: float) -> int: func _ready(): _NetfoxLogger.register_tag(func(): return "@%d" % tick, -100) + + _channel_manager = ChannelManager.new() + if _channel_manager.is_enabled(): + rpc_config("_submit_sync_success", { + "rpc_mode": 3, # MultiplayerAPI.RPC_MODE_ANY_PEER + "transfer_mode": 2, # MultiplayerPeer.TRANSFER_MODE_RELIABLE + "call_local": true, + "channel": _channel_manager.get_channel() + }) + else: + rpc_config("_submit_sync_success", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": true, + }) _tickrate_handshake = NetworkTickrateHandshake.new() add_child(_tickrate_handshake) @@ -567,7 +589,6 @@ func _notification(what): func _is_active() -> bool: return _state == _STATE_ACTIVE -@rpc("any_peer", "reliable", "call_local") func _submit_sync_success(): var peer_id = multiplayer.get_remote_sender_id() diff --git a/addons/netfox/rollback/rollback-synchronizer.gd b/addons/netfox/rollback/rollback-synchronizer.gd index 0509ad6a..1607ecaf 100644 --- a/addons/netfox/rollback/rollback-synchronizer.gd +++ b/addons/netfox/rollback/rollback-synchronizer.gd @@ -325,6 +325,65 @@ func _enter_tree(): if not NetworkTime.is_initial_sync_done(): # Wait for time sync to complete await NetworkTime.after_sync + + if NetworkTime._channel_manager.is_enabled(): + rpc_config("_submit_inputs", { + "rpc_mode": 3, + "transfer_mode": 1, + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + rpc_config("_submit_full_state", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + rpc_config("_submit_diff_state", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + rpc_config("_ack_full_state", { + "rpc_mode": 3, + "transfer_mode": 0, + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + rpc_config("_ack_diff_state", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + else: + rpc_config("_submit_inputs", { + "rpc_mode": 3, + "transfer_mode": 1, + "call_local": false, + }) + rpc_config("_submit_full_state", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + }) + rpc_config("_submit_diff_state", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + }) + rpc_config("_ack_full_state", { + "rpc_mode": 3, + "transfer_mode": 0, + "call_local": false, + }) + rpc_config("_ack_diff_state", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + }) + _connect_signals.call_deferred() process_settings.call_deferred() @@ -596,7 +655,6 @@ func _sanitize_by_authority(snapshot: Dictionary, sender: int) -> Dictionary: return sanitized -@rpc("any_peer", "unreliable", "call_remote") func _submit_inputs(inputs: Array, tick: int): if not _is_initialized: # Settings not processed yet @@ -629,7 +687,6 @@ func _submit_inputs(inputs: Array, tick: int): else: _logger.warning("Received invalid input from %s for tick %s for %s" % [sender, tick, root.name]) -@rpc("any_peer", "unreliable_ordered", "call_remote") func _submit_full_state(state: Dictionary, tick: int): if not _is_initialized: # Settings not processed yet @@ -654,7 +711,6 @@ func _submit_full_state(state: Dictionary, tick: int): if NetworkRollback.enable_diff_states: _ack_full_state.rpc_id(sender, tick) -@rpc("any_peer", "unreliable_ordered", "call_remote") func _submit_diff_state(diff_state: Dictionary, tick: int, reference_tick: int): if not _is_initialized: # Settings not processed yet @@ -693,14 +749,12 @@ func _submit_diff_state(diff_state: Dictionary, tick: int, reference_tick: int): _ack_diff_state.rpc_id(sender, tick) _next_diff_ack_tick = tick + diff_ack_interval -@rpc("any_peer", "reliable", "call_remote") func _ack_full_state(tick: int): var sender_id := multiplayer.get_remote_sender_id() _ackd_state[sender_id] = tick _logger.trace("Peer %d ack'd full state for tick %d", [sender_id, tick]) -@rpc("any_peer", "unreliable_ordered", "call_remote") func _ack_diff_state(tick: int): var sender_id := multiplayer.get_remote_sender_id() _ackd_state[sender_id] = tick diff --git a/addons/netfox/state-synchronizer.gd b/addons/netfox/state-synchronizer.gd index 333909a6..4a773e76 100644 --- a/addons/netfox/state-synchronizer.gd +++ b/addons/netfox/state-synchronizer.gd @@ -73,6 +73,20 @@ func _disconnect_signals(): func _enter_tree(): if Engine.is_editor_hint(): return + + if ChannelManager.is_enabled(): + rpc_config("_submit_state", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + else: + rpc_config("_submit_state", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + }) _connect_signals.call_deferred() process_settings.call_deferred() @@ -99,7 +113,6 @@ func _reprocess_settings(): _properties_dirty = false process_settings() -@rpc("authority", "unreliable", "call_remote") func _submit_state(state: Dictionary, tick: int): if tick <= _last_received_tick: return diff --git a/addons/netfox/time/network-tickrate-handshake.gd b/addons/netfox/time/network-tickrate-handshake.gd index 214c959f..4234daf5 100644 --- a/addons/netfox/time/network-tickrate-handshake.gd +++ b/addons/netfox/time/network-tickrate-handshake.gd @@ -59,6 +59,19 @@ func stop() -> void: func _ready() -> void: name = "NetworkTickrateHandshake" + if NetworkTime._channel_manager.is_enabled(): + rpc_config("_submit_tickrate", { + "rpc_mode": 3, # MultiplayerAPI.RPC_MODE_AUTHORITY + "transfer_mode": 2, # MultiplayerPeer.TRANSFER_MODE_RELIABLE + "call_local": false, + "channel": NetworkTime._channel_manager.get_channel() + }) + else: + rpc_config("_submit_tickrate", { + "rpc_mode": 3, + "transfer_mode": 2, + "call_local": false, + }) func _handle_new_peer(peer: int): if multiplayer.is_server(): @@ -89,7 +102,6 @@ func _handle_tickrate_mismatch(peer: int, tickrate: int) -> void: SIGNAL: on_tickrate_mismatch.emit(peer, tickrate) -@rpc("any_peer", "reliable", "call_remote") func _submit_tickrate(tickrate: int) -> void: var sender = multiplayer.get_remote_sender_id() _logger.debug("Received tickrate %d from peer %d", [tickrate, sender])