extends Node
class_name _NetworkPerformance

const NETWORK_LOOP_DURATION_MONITOR: StringName = &"netfox/Network loop duration (ms)"
const ROLLBACK_LOOP_DURATION_MONITOR: StringName = &"netfox/Rollback loop duration (ms)"
const NETWORK_TICKS_MONITOR: StringName = &"netfox/Network ticks simulated"
const ROLLBACK_TICKS_MONITOR: StringName = &"netfox/Rollback ticks simulated"
const ROLLBACK_TICK_DURATION_MONITOR: StringName = &"netfox/Rollback tick duration (ms)"
const ROLLBACK_NODES_SIMULATED_MONITOR: StringName = &"netfox/Rollback nodes simulated"
const ROLLBACK_NODES_SIMULATED_PER_TICK_MONITOR: StringName = &"netfox/Rollback nodes simulated per tick (avg)"

const FULL_STATE_PROPERTIES_COUNT: StringName = &"netfox/Full state properties count"
const SENT_STATE_PROPERTIES_COUNT: StringName = &"netfox/Sent state properties count"
const SENT_STATE_PROPERTIES_RATIO: StringName = &"netfox/Sent state properties ratio"

var _network_loop_start: float = 0
var _network_loop_duration: float = 0

var _network_ticks: int = 0
var _network_ticks_accum: int = 0

var _rollback_loop_start: float = 0
var _rollback_loop_duration: float = 0

var _rollback_ticks: int = 0
var _rollback_ticks_accum: int = 0

var _rollback_nodes_simulated: int = 0
var _rollback_nodes_simulated_accum: int = 0

var _full_state_props: int = 0
var _full_state_props_accum: int = 0

var _sent_state_props: int = 0
var _sent_state_props_accum: int = 0

static var _logger: NetfoxLogger = NetfoxLogger._for_netfox("NetworkPerformance")

## Check if performance monitoring is enabled.
## [br][br]
## By default, monitoring is only enabled in debug builds 
## ( see [method OS.is_debug_build] ). [br]
## Can be forced on with the [code]netfox_perf[/code] feature tag. [br]
## Can be forced off with the [code]netfox_noperf[/code] feature tag.
func is_enabled() -> bool:
	if OS.has_feature("netfox_noperf"):
		return false
	
	if OS.has_feature("netfox_perf"):
		return true

	# This returns true in the editor too
	return OS.is_debug_build()

## Get time spent in the last network tick loop, in millisec.
## [br]
## Note that this also includes time spent in the rollback tick loop.
func get_network_loop_duration_ms() -> float:
	return _network_loop_duration * 1000

## Get the number of ticks simulated in the last network tick loop.
func get_network_ticks() -> int:
	return _network_ticks

## Get time spent in the last rollback tick loop, in millisec.
func get_rollback_loop_duration_ms() -> float:
	return _rollback_loop_duration * 1000

## Get the number of ticks resimulated in the last rollback tick loop.
func get_rollback_ticks() -> int:
	return _rollback_ticks

## Get the average amount of time spent in a rollback tick during the last
## rollback loop, in millisec.
func get_rollback_tick_duration_ms() -> float:
	return _rollback_loop_duration * 1000 / maxi(_rollback_ticks, 1)

## Get the number of nodes simulated during the last rollback loop.
func get_rollback_nodes_simulated() -> int:
	return _rollback_nodes_simulated

## Get the number of nodes simulated per tick on average during the last
## rollback loop.
func get_rollback_nodes_simulated_per_tick() -> float:
	return _rollback_nodes_simulated / maxf(1., _rollback_ticks)

func push_rollback_nodes_simulated(count: int):
	_rollback_nodes_simulated_accum += count

## Get the number of properties in the full state recorded during the last tick
## loop.
func get_full_state_props_count() -> int:
	return _full_state_props

## Get the number of properties actually sent during the last tick loop.
func get_sent_state_props_count() -> int:
	return _sent_state_props

## Get the ratio of sent properties count to full state properties count.
##
## See [member get_full_state_props_count][br]
## See [member get_sent_state_props_count]
func get_sent_state_props_ratio() -> float:
	return _sent_state_props / maxf(1., _full_state_props)

func push_full_state(state: Dictionary) -> void:
	_full_state_props_accum += state.size()

func push_full_state_props(count: int) -> void:
	_full_state_props_accum += count

func push_full_state_broadcast(state: Dictionary) -> void:
	_full_state_props_accum += state.size() * (multiplayer.get_peers().size() - 1)

func push_sent_state(state: Dictionary) -> void:
	_sent_state_props_accum += state.size()

func push_sent_state_props(count: int) -> void:
	_sent_state_props_accum += count

func push_sent_state_broadcast(state: Dictionary) -> void:
	_sent_state_props_accum += state.size() * (multiplayer.get_peers().size() - 1)

func _ready() -> void:
	if not is_enabled():
		_logger.debug("Network performance disabled")
		return

	_logger.debug("Network performance enabled, registering performance monitors")
	Performance.add_custom_monitor(NETWORK_LOOP_DURATION_MONITOR, get_network_loop_duration_ms)
	Performance.add_custom_monitor(ROLLBACK_LOOP_DURATION_MONITOR, get_rollback_loop_duration_ms)
	Performance.add_custom_monitor(NETWORK_TICKS_MONITOR, get_network_ticks)
	Performance.add_custom_monitor(ROLLBACK_TICKS_MONITOR, get_rollback_ticks)
	Performance.add_custom_monitor(ROLLBACK_TICK_DURATION_MONITOR, get_rollback_tick_duration_ms)
	Performance.add_custom_monitor(ROLLBACK_NODES_SIMULATED_MONITOR, get_rollback_nodes_simulated)
	Performance.add_custom_monitor(ROLLBACK_NODES_SIMULATED_PER_TICK_MONITOR, get_rollback_nodes_simulated_per_tick)
	
	Performance.add_custom_monitor(FULL_STATE_PROPERTIES_COUNT, get_full_state_props_count)
	Performance.add_custom_monitor(SENT_STATE_PROPERTIES_COUNT, get_sent_state_props_count)
	Performance.add_custom_monitor(SENT_STATE_PROPERTIES_RATIO, get_sent_state_props_ratio)
	
	NetworkTime.before_tick_loop.connect(_before_tick_loop)
	NetworkTime.on_tick.connect(_on_network_tick)
	NetworkTime.after_tick_loop.connect(_after_tick_loop)
	
	NetworkRollback.before_loop.connect(_before_rollback_loop)
	NetworkRollback.on_process_tick.connect(_on_rollback_tick)
	NetworkRollback.after_loop.connect(_after_rollback_loop)

func _before_tick_loop() -> void:
	_network_loop_start = _time()
	_network_ticks_accum = 0

func _on_network_tick(_dt, _t) -> void:
	_network_ticks_accum += 1

func _after_tick_loop() -> void:
	_network_loop_duration = _time() - _network_loop_start
	_network_ticks = _network_ticks_accum
	
	_full_state_props = _full_state_props_accum
	_full_state_props_accum = 0
	
	_sent_state_props = _sent_state_props_accum
	_sent_state_props_accum = 0

func _before_rollback_loop() -> void:
	_rollback_loop_start = _time()
	_rollback_ticks_accum = 0
	_rollback_nodes_simulated_accum = 0

func _on_rollback_tick(_t: int) -> void:
	_rollback_ticks_accum += 1

func _after_rollback_loop() -> void:
	_rollback_loop_duration = _time() - _rollback_loop_start
	_rollback_ticks = _rollback_ticks_accum
	_rollback_nodes_simulated = _rollback_nodes_simulated_accum

func _time() -> float:
	return Time.get_unix_time_from_system()
