Rollback Refactor - _HistoryBuffer Addition#397
Rollback Refactor - _HistoryBuffer Addition#397DustieDog wants to merge 5 commits intofoxssake:epic/refactor-rollback-synchronizerfrom
Conversation
Bugged but complete RollbackSync
Data now properly sends over RPC. Introduces a bug where clients move faster than expected, and also get local artifacting.
There was a problem hiding this comment.
Plan to rename this PropertySnapshot prior to the rollback refactor being merged to main. Using unique name for now to avoid potential conflicts with existing property-snapshot.gd references until we're sure it's safe to switch.
elementbound
left a comment
There was a problem hiding this comment.
Kudos for the PR! Added some comments. Feel free to leave the unit tests for the end, I'm happy to add them.
There was a problem hiding this comment.
I see this class is already specialized to snapshots. Is the original HistoryBuffer > PropertyHistoryBuffer combo intentionally skipped?
There was a problem hiding this comment.
Also note to self, since this is now refactored into its own class, we can now add unit tests!
There was a problem hiding this comment.
I see this class is already specialized to snapshots. Is the original HistoryBuffer > PropertyHistoryBuffer combo intentionally skipped?
PropertyHistoryBuffer is just a dictionary. Taking your previous comment:
No, that's PropertyHistoryBuffer. The distinction is analogous to List<T> vs. List<StateSnapshot>
HistoryBuffer is just a Dictionary[String, PropertyStoreSnapshot], there's nothing I can see that can be refactored out of that which a Dictionary[String, T] doesn't already do. If a use case pops up later for something of the sort we can look at it then though.
There was a problem hiding this comment.
I'd prefer to have a generic collection class ( HistoryBuffer ), and a specialized one extending it, that we use. Part of it is pure preference of having generic collections, part of it is that it would enable netfox to provide users a way to associate arbitrary data ( not just snapshots ) with ticks.
there's nothing I can see that can be refactored out of that which a Dictionary[String, T] doesn't already do
I believemergeis one such method that is specific to property snapshots.
| extends Object | ||
| class_name PropertyStoreSnapshot | ||
|
|
||
| # Typed as Dictionary[String, Property] |
There was a problem hiding this comment.
While at it, what do you think about semantically documenting dictionaries? E.g.:
| # Typed as Dictionary[String, Property] | |
| # Maps property path strings to Property instances |
There was a problem hiding this comment.
Dictionary[String, Property] is more direct and therefore readable to me personally, perhaps both are warranted?
| var input := inputs[offset] as Dictionary | ||
| if input == null: | ||
|
|
||
| var input := PropertyStoreSnapshot.deserialize(inputs[offset] as Dictionary) |
There was a problem hiding this comment.
Is the cast needed here?
There was a problem hiding this comment.
It was at one point to fix an RPC error, further testing is needed to see if it's still required.
|
|
||
| var input := PropertyStoreSnapshot.deserialize(inputs[offset] as Dictionary) | ||
|
|
||
| if input.size() == 0: |
There was a problem hiding this comment.
This got swapped with the original null-check. The original idea was to prevent against accidental nulls present in the received input array.
I'd recommend something like this instead:
| var input := PropertyStoreSnapshot.deserialize(inputs[offset] as Dictionary) | |
| if input.size() == 0: | |
| var raw_input := inputs[offset] as Dictionary | |
| if raw_input == null: | |
| # We've somehow received a null input - shouldn't happen | |
| _logger.error("Null input received for %d, full batch is %s", [input_tick, inputs]) | |
| continue | |
| var input := PropertyStoreSnapshot.deserialize(raw_input) |
Note: Couldn't comment on the unchanged parts so this is not safe to commit as-is 😄
There was a problem hiding this comment.
What are your thoughts of moving this into PropertyStoreSnapshot.deserialize() instead?
| if not sanitized.is_empty(): | ||
| var known_input := _inputs.get(input_tick) | ||
| var known_input := _inputs.get_snapshot(input_tick) | ||
| # TODO: Does this work with PropertyStoreSnapshot? |
There was a problem hiding this comment.
Probably not, as these are compared by reference IIRC. A unit test would decide though 😄 I'm not aware of any way to override the == operator in Godot, so our best bet to write a .equals() method.
| return | ||
|
|
||
|
|
||
| var deserialized = PropertyStoreSnapshot.deserialize(state) |
There was a problem hiding this comment.
| var deserialized = PropertyStoreSnapshot.deserialize(state) | |
| var deserialized := PropertyStoreSnapshot.deserialize(state) |
DustieDog
left a comment
There was a problem hiding this comment.
Reviewed @elementbound's notes, no disagreements.
| _states.clear_history() | ||
| _inputs.clear_history() |
There was a problem hiding this comment.
Should calls to non-dictionaries follow dictionary conventions? IE should these functions be named clear()?
There was a problem hiding this comment.
Good catch, missed this during the in-depth review. I'd prefer that, especially since clearing history is a given for a history buffer.
There was a problem hiding this comment.
I see this class is already specialized to snapshots. Is the original HistoryBuffer > PropertyHistoryBuffer combo intentionally skipped?
PropertyHistoryBuffer is just a dictionary. Taking your previous comment:
No, that's PropertyHistoryBuffer. The distinction is analogous to List<T> vs. List<StateSnapshot>
HistoryBuffer is just a Dictionary[String, PropertyStoreSnapshot], there's nothing I can see that can be refactored out of that which a Dictionary[String, T] doesn't already do. If a use case pops up later for something of the sort we can look at it then though.
| _retrieved_tick = earliest_tick | ||
| return buffer[earliest_tick] | ||
| func _sanitize_by_authority(snapshot: PropertyStoreSnapshot, sender: int) -> PropertyStoreSnapshot: | ||
| var sanitized := PropertyStoreSnapshot.new() |
There was a problem hiding this comment.
I thought I read somewhere that modifying dictionaries in a for loop can cause issues, but I can't find a reference.
| var input := inputs[offset] as Dictionary | ||
| if input == null: | ||
|
|
||
| var input := PropertyStoreSnapshot.deserialize(inputs[offset] as Dictionary) |
There was a problem hiding this comment.
It was at one point to fix an RPC error, further testing is needed to see if it's still required.
|
|
||
| var input := PropertyStoreSnapshot.deserialize(inputs[offset] as Dictionary) | ||
|
|
||
| if input.size() == 0: |
There was a problem hiding this comment.
What are your thoughts of moving this into PropertyStoreSnapshot.deserialize() instead?
| func get_earliest_tick() -> int: | ||
| return _history.keys().min() | ||
|
|
||
| func get_latest_tick() -> int: | ||
| return _history.keys().max() |
There was a problem hiding this comment.
It might make sense to cache these, if these methods keep being used in the final PR. Needs benchmarking.
|
Closing in favour of #399. |
Draft PR for #385.
Currently the client is bugged, resulting in local artifacts and unintended behaviour on the host (Client player doesn't move smoothly, and moves faster than expected in Forest Brawl)