mirror of
https://codeberg.org/sunder/sunder.git
synced 2026-03-07 10:50:25 +00:00
145 lines
4.4 KiB
GDScript
145 lines
4.4 KiB
GDScript
# This file is part of sunder.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
## This defines a ping manager.
|
|
##
|
|
## When added to your scene and connect the [member synchronized] signal to your
|
|
## custom handler in order to receive ping updates on a regular basis (as
|
|
## defined by [param ping_interval] and synchronize the state for each peer.
|
|
## [codeblock]
|
|
## func _ready() -> void:
|
|
## $Ping.synchronized.connect(_on_ping_sync)
|
|
##
|
|
## func _on_ping_sync(state: Dictionary) -> void:
|
|
## for peer_id in state:
|
|
## print("peer `%s` latency is %d ms" % [peer_id, state[peer_id]])
|
|
## [/codeblock]
|
|
@icon("res://assets/icons/ping.svg")
|
|
class_name Ping extends Timer
|
|
|
|
## Emitted when the state is synchronized.
|
|
signal synchronized(state : Dictionary)
|
|
|
|
## The size of the ping history buffer used to store past ping times for each client.
|
|
@export var ping_history: int = 8:
|
|
set = set_ping_history
|
|
|
|
## The ping state being synchronized to connected peers
|
|
@export var _state := {}
|
|
|
|
## The dictionary of [PingInfo] for each connected peer id
|
|
var _infos := {}
|
|
|
|
## The array of times when a ping request occured
|
|
var _pings := []
|
|
var _last_ping := 0
|
|
|
|
## This class defines a ping information to keep track of ping results
|
|
## and provide average ping.
|
|
class PingInfo:
|
|
const SIZE := 32
|
|
var samples := PackedInt32Array()
|
|
var pos := 0
|
|
|
|
func _init() -> void:
|
|
samples.resize(SIZE)
|
|
samples.fill(0)
|
|
|
|
func get_average() -> int:
|
|
var value := 0
|
|
for sample in samples:
|
|
value += sample
|
|
@warning_ignore("integer_division")
|
|
return value / SIZE if value > 0 else 0
|
|
|
|
func set_next(value: int) -> void:
|
|
samples[pos] = value
|
|
pos = (pos + 1) % SIZE
|
|
|
|
func _ready() -> void:
|
|
clear_pings()
|
|
multiplayer.peer_connected.connect(_add_peer)
|
|
multiplayer.peer_disconnected.connect(_del_peer)
|
|
timeout.connect(_on_timeout)
|
|
|
|
func _on_timeout() -> void:
|
|
if _infos:
|
|
# check and update ping intervals
|
|
var now := Time.get_ticks_msec()
|
|
if now >= _pings[_last_ping] + wait_time:
|
|
_last_ping = (_last_ping + 1) % ping_history
|
|
_pings[_last_ping] = now
|
|
_ping.rpc(now)
|
|
# reset state
|
|
_state.clear()
|
|
# iterate over registered clients
|
|
for peer_id:int in _infos:
|
|
# set client average ping state
|
|
_state[peer_id] = _infos[peer_id].get_average()
|
|
# send state to connected peers
|
|
_synchronize.rpc(_state)
|
|
|
|
## Clears the ping history array.
|
|
func clear_pings() -> void:
|
|
_last_ping = 0
|
|
_pings.resize(ping_history)
|
|
_pings.fill(0)
|
|
|
|
## Sets the size of the ping history buffer and clears existing ping data.
|
|
func set_ping_history(value: int) -> void:
|
|
if value < 1:
|
|
return
|
|
ping_history = value
|
|
clear_pings()
|
|
|
|
## Adds a new peer to the registry
|
|
func _add_peer(peer_id: int) -> void:
|
|
_infos[peer_id] = PingInfo.new()
|
|
|
|
## Deletes a peer from the registry
|
|
func _del_peer(peer_id: int) -> void:
|
|
_infos.erase(peer_id)
|
|
|
|
@rpc("authority", "call_remote", "unreliable")
|
|
func _ping(time: int) -> void:
|
|
_pong.rpc_id(get_multiplayer_authority(), time)
|
|
|
|
@rpc("any_peer", "call_remote", "unreliable")
|
|
func _pong(time: int) -> void:
|
|
if not multiplayer.is_server():
|
|
return
|
|
# get id of the peer sending the pong
|
|
var peer_id: int = multiplayer.get_remote_sender_id()
|
|
# check if peer exists in the registered clients dictionary
|
|
if not _infos.has(peer_id):
|
|
return
|
|
# init variables
|
|
var now := Time.get_ticks_msec()
|
|
var last := (_last_ping + 1) % ping_history
|
|
var found := ping_history * wait_time
|
|
# search related ping in history
|
|
for i in range(ping_history):
|
|
# check if current ping matches received pong
|
|
if time == _pings[last]:
|
|
found = _pings[last]
|
|
break
|
|
# move to next ping in history
|
|
last = (last + 1) % ping_history
|
|
# compute round-trip time (RTT) and update ping info for this peer
|
|
_infos[peer_id].set_next((now - found) / 2)
|
|
|
|
@rpc("authority", "call_local", "unreliable")
|
|
func _synchronize(state: Dictionary) -> void:
|
|
_state = state
|
|
synchronized.emit(_state)
|