|
|
|
@@ -0,0 +1,282 @@
|
|
|
|
|
extends Node
|
|
|
|
|
class_name _RollbackHistoryTransmitter
|
|
|
|
|
|
|
|
|
|
var root: Node
|
|
|
|
|
var enable_input_broadcast: bool = true
|
|
|
|
|
var full_state_interval: int
|
|
|
|
|
var diff_ack_interval: int
|
|
|
|
|
|
|
|
|
|
# Provided externally by RBS
|
|
|
|
|
var _state_history: _PropertyHistoryBuffer
|
|
|
|
|
var _input_history: _PropertyHistoryBuffer
|
|
|
|
|
var _visibility_filter: PeerVisibilityFilter
|
|
|
|
|
|
|
|
|
|
var _state_property_config: _PropertyConfig
|
|
|
|
|
var _input_property_config: _PropertyConfig
|
|
|
|
|
|
|
|
|
|
var _property_cache: PropertyCache
|
|
|
|
|
var _skipset: _Set
|
|
|
|
|
|
|
|
|
|
# Collaborators
|
|
|
|
|
var _input_encoder: _RedundantHistoryEncoder
|
|
|
|
|
var _full_state_encoder: _SnapshotHistoryEncoder
|
|
|
|
|
var _diff_state_encoder: _DiffHistoryEncoder
|
|
|
|
|
|
|
|
|
|
# State
|
|
|
|
|
var _ackd_state: Dictionary = {}
|
|
|
|
|
var _next_full_state_tick: int
|
|
|
|
|
var _next_diff_ack_tick: int
|
|
|
|
|
|
|
|
|
|
var _earliest_input_tick: int
|
|
|
|
|
var _latest_state_tick: int
|
|
|
|
|
|
|
|
|
|
var _is_predicted_tick: bool
|
|
|
|
|
var _is_initialized: bool
|
|
|
|
|
|
|
|
|
|
# Signals
|
|
|
|
|
signal _on_transmit_state(state: Dictionary, tick: int)
|
|
|
|
|
|
|
|
|
|
static var _logger: NetfoxLogger = NetfoxLogger._for_netfox("RollbackHistoryTransmitter")
|
|
|
|
|
|
|
|
|
|
func get_earliest_input_tick() -> int:
|
|
|
|
|
return _earliest_input_tick
|
|
|
|
|
|
|
|
|
|
func get_latest_state_tick() -> int:
|
|
|
|
|
return _latest_state_tick
|
|
|
|
|
|
|
|
|
|
func set_predicted_tick(p_is_predicted_tick) -> void:
|
|
|
|
|
_is_predicted_tick = p_is_predicted_tick
|
|
|
|
|
|
|
|
|
|
func sync_settings(p_root: Node, p_enable_input_broadcast: bool, p_full_state_interval: int, p_diff_ack_interval: int) -> void:
|
|
|
|
|
root = p_root
|
|
|
|
|
enable_input_broadcast = p_enable_input_broadcast
|
|
|
|
|
full_state_interval = p_full_state_interval
|
|
|
|
|
diff_ack_interval = p_diff_ack_interval
|
|
|
|
|
|
|
|
|
|
func configure(
|
|
|
|
|
p_state_history: _PropertyHistoryBuffer, p_input_history: _PropertyHistoryBuffer,
|
|
|
|
|
p_state_property_config: _PropertyConfig, p_input_property_config: _PropertyConfig,
|
|
|
|
|
p_visibility_filter: PeerVisibilityFilter,
|
|
|
|
|
p_property_cache: PropertyCache,
|
|
|
|
|
p_skipset: _Set
|
|
|
|
|
) -> void:
|
|
|
|
|
_state_history = p_state_history
|
|
|
|
|
_input_history = p_input_history
|
|
|
|
|
_state_property_config = p_state_property_config
|
|
|
|
|
_input_property_config = p_input_property_config
|
|
|
|
|
_visibility_filter = p_visibility_filter
|
|
|
|
|
_property_cache = p_property_cache
|
|
|
|
|
_skipset = p_skipset
|
|
|
|
|
|
|
|
|
|
_input_encoder = _RedundantHistoryEncoder.new(_input_history, _property_cache)
|
|
|
|
|
_full_state_encoder = _SnapshotHistoryEncoder.new(_state_history, _property_cache)
|
|
|
|
|
_diff_state_encoder = _DiffHistoryEncoder.new(_state_history, _property_cache)
|
|
|
|
|
|
|
|
|
|
_is_initialized = true
|
|
|
|
|
|
|
|
|
|
reset()
|
|
|
|
|
|
|
|
|
|
func reset() -> void:
|
|
|
|
|
_ackd_state.clear()
|
|
|
|
|
_latest_state_tick = NetworkTime.tick - 1
|
|
|
|
|
_earliest_input_tick = NetworkTime.tick
|
|
|
|
|
_next_full_state_tick = NetworkTime.tick
|
|
|
|
|
_next_diff_ack_tick = NetworkTime.tick
|
|
|
|
|
|
|
|
|
|
# Scatter full state sends, so not all nodes send at the same tick
|
|
|
|
|
if is_inside_tree():
|
|
|
|
|
_next_full_state_tick += hash(root.get_path()) % maxi(1, full_state_interval)
|
|
|
|
|
_next_diff_ack_tick += hash(root.get_path()) % maxi(1, diff_ack_interval)
|
|
|
|
|
else:
|
|
|
|
|
_next_full_state_tick += hash(root.name) % maxi(1, full_state_interval)
|
|
|
|
|
_next_diff_ack_tick += hash(root.name) % maxi(1, diff_ack_interval)
|
|
|
|
|
|
|
|
|
|
_diff_state_encoder.add_properties(_state_property_config.get_properties())
|
|
|
|
|
_full_state_encoder.set_properties(_get_owned_state_props())
|
|
|
|
|
_input_encoder.set_properties(_get_owned_input_props())
|
|
|
|
|
|
|
|
|
|
func conclude_tick_loop() -> void:
|
|
|
|
|
_earliest_input_tick = NetworkTime.tick
|
|
|
|
|
|
|
|
|
|
func transmit_input(tick: int) -> void:
|
|
|
|
|
if not _get_owned_input_props().is_empty():
|
|
|
|
|
var input_tick: int = tick + NetworkRollback.input_delay
|
|
|
|
|
var input_data := _input_encoder.encode(input_tick, _get_owned_input_props())
|
|
|
|
|
var state_owning_peer := root.get_multiplayer_authority()
|
|
|
|
|
NetworkRollback.register_input_submission(root, tick)
|
|
|
|
|
|
|
|
|
|
if enable_input_broadcast:
|
|
|
|
|
for peer in _visibility_filter.get_rpc_target_peers():
|
|
|
|
|
_submit_input.rpc_id(peer, input_tick, input_data)
|
|
|
|
|
elif state_owning_peer != multiplayer.get_unique_id():
|
|
|
|
|
_submit_input.rpc_id(state_owning_peer, input_tick, input_data)
|
|
|
|
|
|
|
|
|
|
func transmit_state(tick: int) -> void:
|
|
|
|
|
if _get_owned_state_props().is_empty():
|
|
|
|
|
# We don't own state, don't transmit anything
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if _is_predicted_tick and not _input_property_config.get_properties().is_empty():
|
|
|
|
|
# Don't transmit anything if we're predicting
|
|
|
|
|
# EXCEPT when we're running inputless
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Include properties we own
|
|
|
|
|
var full_state := _PropertySnapshot.new()
|
|
|
|
|
|
|
|
|
|
for property in _get_owned_state_props():
|
|
|
|
|
if _should_broadcast(property, tick):
|
|
|
|
|
full_state.set_value(property.to_string(), property.get_value())
|
|
|
|
|
|
|
|
|
|
_on_transmit_state.emit(full_state, tick)
|
|
|
|
|
|
|
|
|
|
# No properties to send?
|
|
|
|
|
if full_state.is_empty():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
_latest_state_tick = max(_latest_state_tick, tick)
|
|
|
|
|
|
|
|
|
|
var is_sending_diffs := NetworkRollback.enable_diff_states
|
|
|
|
|
var is_full_state_tick := not is_sending_diffs or (full_state_interval > 0 and tick > _next_full_state_tick)
|
|
|
|
|
|
|
|
|
|
if is_full_state_tick:
|
|
|
|
|
# Broadcast new full state
|
|
|
|
|
for peer in _visibility_filter.get_rpc_target_peers():
|
|
|
|
|
_send_full_state(tick, peer)
|
|
|
|
|
|
|
|
|
|
# Adjust next full state if sending diffs
|
|
|
|
|
if is_sending_diffs:
|
|
|
|
|
_next_full_state_tick = tick + full_state_interval
|
|
|
|
|
else:
|
|
|
|
|
# Send diffs to each peer
|
|
|
|
|
for peer in _visibility_filter.get_visible_peers():
|
|
|
|
|
var reference_tick := _ackd_state.get(peer, -1) as int
|
|
|
|
|
if reference_tick < 0 or not _state_history.has(reference_tick):
|
|
|
|
|
# Peer hasn't ack'd any tick, or we don't have the ack'd tick
|
|
|
|
|
# Send full state
|
|
|
|
|
_send_full_state(tick, peer)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Prepare diff
|
|
|
|
|
var diff_state_data := _diff_state_encoder.encode(tick, reference_tick, _get_owned_state_props())
|
|
|
|
|
|
|
|
|
|
if _diff_state_encoder.get_full_snapshot().size() == _diff_state_encoder.get_encoded_snapshot().size():
|
|
|
|
|
# State is completely different, send full state
|
|
|
|
|
_send_full_state(tick, peer)
|
|
|
|
|
else:
|
|
|
|
|
# Send only diff
|
|
|
|
|
_submit_diff_state.rpc_id(peer, diff_state_data, tick, reference_tick)
|
|
|
|
|
|
|
|
|
|
# Push metrics
|
|
|
|
|
NetworkPerformance.push_full_state(_diff_state_encoder.get_full_snapshot())
|
|
|
|
|
NetworkPerformance.push_sent_state(_diff_state_encoder.get_encoded_snapshot())
|
|
|
|
|
|
|
|
|
|
func _should_broadcast(property: PropertyEntry, tick: int) -> bool:
|
|
|
|
|
# Only broadcast if we've simulated the node
|
|
|
|
|
# NOTE: _can_simulate checks mutations, but to override _skipset
|
|
|
|
|
# we check first
|
|
|
|
|
if NetworkRollback.is_mutated(property.node, tick - 1):
|
|
|
|
|
return true
|
|
|
|
|
if _skipset.has(property.node):
|
|
|
|
|
return false
|
|
|
|
|
if NetworkRollback.is_rollback_aware(property.node):
|
|
|
|
|
return NetworkRollback.is_simulated(property.node)
|
|
|
|
|
|
|
|
|
|
# Node is not rollback-aware, broadcast updates only if we own it
|
|
|
|
|
return property.node.is_multiplayer_authority()
|
|
|
|
|
|
|
|
|
|
func _send_full_state(tick: int, peer: int = 0) -> void:
|
|
|
|
|
var full_state_snapshot := _state_history.get_snapshot(tick).as_dictionary()
|
|
|
|
|
var full_state_data := _full_state_encoder.encode(tick, _get_owned_state_props())
|
|
|
|
|
|
|
|
|
|
_submit_full_state.rpc_id(peer, full_state_data, tick)
|
|
|
|
|
|
|
|
|
|
if peer <= 0:
|
|
|
|
|
NetworkPerformance.push_full_state_broadcast(full_state_snapshot)
|
|
|
|
|
NetworkPerformance.push_sent_state_broadcast(full_state_snapshot)
|
|
|
|
|
else:
|
|
|
|
|
NetworkPerformance.push_full_state(full_state_snapshot)
|
|
|
|
|
NetworkPerformance.push_sent_state(full_state_snapshot)
|
|
|
|
|
|
|
|
|
|
func _notification(what):
|
|
|
|
|
if what == NOTIFICATION_PREDELETE:
|
|
|
|
|
NetworkRollback.free_input_submission_data_for(root)
|
|
|
|
|
|
|
|
|
|
@rpc("any_peer", "unreliable", "call_remote")
|
|
|
|
|
func _submit_input(tick: int, data: Array) -> void:
|
|
|
|
|
if not _is_initialized:
|
|
|
|
|
# Settings not processed yet
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var sender := multiplayer.get_remote_sender_id()
|
|
|
|
|
var snapshots := _input_encoder.decode(data, _input_property_config.get_properties_owned_by(sender))
|
|
|
|
|
var earliest_received_input = _input_encoder.apply(tick, snapshots, sender)
|
|
|
|
|
if earliest_received_input >= 0:
|
|
|
|
|
_earliest_input_tick = mini(_earliest_input_tick, earliest_received_input)
|
|
|
|
|
NetworkRollback.register_input_submission(root, tick)
|
|
|
|
|
|
|
|
|
|
# `serialized_state` is a serialized _PropertySnapshot
|
|
|
|
|
@rpc("any_peer", "unreliable_ordered", "call_remote")
|
|
|
|
|
func _submit_full_state(data: Array, tick: int) -> void:
|
|
|
|
|
if not _is_initialized:
|
|
|
|
|
# Settings not processed yet
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var sender := multiplayer.get_remote_sender_id()
|
|
|
|
|
var snapshot := _full_state_encoder.decode(data, _state_property_config.get_properties_owned_by(sender))
|
|
|
|
|
if not _full_state_encoder.apply(tick, snapshot, sender):
|
|
|
|
|
# Invalid data
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
_latest_state_tick = tick
|
|
|
|
|
if NetworkRollback.enable_diff_states:
|
|
|
|
|
_ack_full_state.rpc_id(sender, tick)
|
|
|
|
|
|
|
|
|
|
# State is a serialized _PropertySnapshot (Dictionary[String, Variant])
|
|
|
|
|
@rpc("any_peer", "unreliable_ordered", "call_remote")
|
|
|
|
|
func _submit_diff_state(data: PackedByteArray, tick: int, reference_tick: int) -> void:
|
|
|
|
|
if not _is_initialized:
|
|
|
|
|
# Settings not processed yet
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var sender = multiplayer.get_remote_sender_id()
|
|
|
|
|
var diff_snapshot := _diff_state_encoder.decode(data, _state_property_config.get_properties_owned_by(sender))
|
|
|
|
|
if not _diff_state_encoder.apply(tick, diff_snapshot, reference_tick, sender):
|
|
|
|
|
# Invalid data
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
_latest_state_tick = tick
|
|
|
|
|
|
|
|
|
|
if NetworkRollback.enable_diff_states:
|
|
|
|
|
if diff_ack_interval > 0 and tick > _next_diff_ack_tick:
|
|
|
|
|
_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) -> void:
|
|
|
|
|
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) -> void:
|
|
|
|
|
var sender_id := multiplayer.get_remote_sender_id()
|
|
|
|
|
_ackd_state[sender_id] = tick
|
|
|
|
|
|
|
|
|
|
_logger.trace("Peer %d ack'd diff state for tick %d", [sender_id, tick])
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Shared utils, extract later
|
|
|
|
|
|
|
|
|
|
func _get_recorded_state_props() -> Array[PropertyEntry]:
|
|
|
|
|
return _state_property_config.get_properties()
|
|
|
|
|
|
|
|
|
|
func _get_owned_state_props() -> Array[PropertyEntry]:
|
|
|
|
|
return _state_property_config.get_owned_properties()
|
|
|
|
|
|
|
|
|
|
func _get_recorded_input_props() -> Array[PropertyEntry]:
|
|
|
|
|
return _input_property_config.get_owned_properties()
|
|
|
|
|
|
|
|
|
|
func _get_owned_input_props() -> Array[PropertyEntry]:
|
|
|
|
|
return _input_property_config.get_owned_properties()
|