Once a player joins, rollback runs from tick 0 to current tick
I'm currently focused on other PRs but this is a performance bug which I have to at least bring up.
The following function is inside the class rollback-synchronizer.gd
The below logger I added, reports that a new player joining, runs the below function from tick 0 to whatever tick you joined.
func _get_history(buffer: Dictionary, tick: int) -> Dictionary:
if buffer.has(tick):
return buffer[tick]
if buffer.is_empty():
return {}
var earliest = buffer.keys().min()
var latest = buffer.keys().max()
if tick < earliest:
_logger.warning("Tried to load tick %s which is earlier than the earliest we have recorded (%s)
Try increasing the history limit." % [tick, earliest])
return buffer[earliest]
#For inputs, this is extrapolation aka prediction
#For example, if you move to the right (at latest)
#at tick, you will still be moving to the right ;)
if tick > latest:
return buffer[latest]
var before: int = buffer.keys() \
.filter(func (key): return key < tick) \
.max()
return buffer[before]
The full stack trace is this:
rollback-synchronizer -> func _prepare_tick(tick: int)
network-rollback -> on_prepare_tick.emit(tick)
network-rollback -> func _rollback()
Below class is network-rollback.gd
func _ready():
NetworkTime.after_tick_loop.connect(_rollback)
func _rollback():
if not enabled:
return
_is_rollback = true
# Ask all rewindables to submit their earliest inputs
_resim_from = NetworkTime.tick
before_loop.emit()
# from = Earliest input amongst all rewindables
var from: int = _resim_from
# to = Current tick
var to: int = NetworkTime.tick
# for tick in from .. to:
for tick in range(from, to): #FORLOOP FROM 0 TO player_joined_at_tick
_tick = tick
_simulated_nodes.clear()
# Prepare state
# Done individually by Rewindables ( usually Rollback Synchronizers )
# Restore input and state for tick
on_prepare_tick.emit(tick) #IT ENTERS HERE
It shouldn't run these loops (imagine if a player joins tick 100000 lol), as at each one, it returns buffer[earliest]
Anyway, needs more investigation, I only stumbled into it while implementing input delay, its not serious even for full games, since normally lobby games start everyone at tick 0 instead of joining at whatever tick
Oh that's a good one! I remember encountering this, maybe I just haven't gotten around to fixing it yet.
I'm considering having a spawned_at field on RollbackSynchronizers to store when they were spawned, and not run any logic on ticks older than that.
Either way, thanks for raising!
I'm considering having a spawned_at field on RollbackSynchronizers to store when they were spawned, and not run any logic on ticks older than that.
This is a great idea, I could even make a PR for it if you wish :)
It is superior to my idea of the player_joined_at_tick variable, since each state is its own thing, instead of caring exclusively for a player (as you mentioned in that other issue, the context of a player changes in each game)
Spent some time on this, some things to add.
The below logger I added, reports that a new player joining, runs the below function from tick 0 to whatever tick you joined.
It's best to log behavior where it's actually done. In this case, NetworkRollback runs the rollback loop and determines the tick range to be resimulated. Adding logs there shows that this does happen, but only on clients.
Turns out the issue itself stemmed from how the example code was set up. RollbackSynchronizer itself would wait until the time is synced to process settings, but BrawlerController did not. So the initial call would set the _latest_state to tick -1, _earliest_input to 0, causing the resimulation.
Removing the manual calls and deferring the process_settings() call leaves enough time for user code to setup ownership.