Fix pause menu input transition races
This commit is contained in:
parent
13fc64b80c
commit
611eeb9d8d
2 changed files with 86 additions and 22 deletions
|
|
@ -284,6 +284,7 @@ func _run() -> void:
|
||||||
start_button.button_index = JOY_BUTTON_START
|
start_button.button_index = JOY_BUTTON_START
|
||||||
start_button.pressed = true
|
start_button.pressed = true
|
||||||
assert(bool(main.call("_is_pause_open_request", start_button)))
|
assert(bool(main.call("_is_pause_open_request", start_button)))
|
||||||
|
await _validate_pause_browser_transition(game_ui, player)
|
||||||
var player_menu := game_ui.get_node("%PlayerMenu") as PlayerMenu
|
var player_menu := game_ui.get_node("%PlayerMenu") as PlayerMenu
|
||||||
player_menu.open_section(PlayerMenu.Section.PROFILE)
|
player_menu.open_section(PlayerMenu.Section.PROFILE)
|
||||||
for _frame: int in 12:
|
for _frame: int in 12:
|
||||||
|
|
@ -565,3 +566,68 @@ func _run() -> void:
|
||||||
await process_frame
|
await process_frame
|
||||||
await create_timer(0.1).timeout
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_pause_browser_transition(
|
||||||
|
game_ui: GameUI,
|
||||||
|
player: Player,
|
||||||
|
) -> void:
|
||||||
|
var pause_menu := game_ui.get_pause_menu()
|
||||||
|
var root_page := pause_menu.get_node("%RootPage") as Control
|
||||||
|
var join_page := pause_menu.get_node("%JoinGamePage") as JoinGamePage
|
||||||
|
pause_menu.open_menu()
|
||||||
|
while bool(pause_menu.get("_root_transition_active")):
|
||||||
|
await process_frame
|
||||||
|
assert(pause_menu.visible and root_page.visible)
|
||||||
|
assert(not bool(player.call("_is_movement_input_enabled")))
|
||||||
|
|
||||||
|
pause_menu.call("_open_join_game")
|
||||||
|
while not join_page.visible:
|
||||||
|
await process_frame
|
||||||
|
assert(bool(pause_menu.get("_root_transition_active")))
|
||||||
|
pause_menu.call("_close_join_game")
|
||||||
|
assert(not join_page.visible)
|
||||||
|
# This is the issue #73 input: Escape arrives while the root is returning.
|
||||||
|
assert(pause_menu.handle_escape())
|
||||||
|
while bool(pause_menu.get("_root_transition_active")):
|
||||||
|
await process_frame
|
||||||
|
assert(pause_menu.visible and root_page.visible)
|
||||||
|
assert(not bool(player.call("_is_movement_input_enabled")))
|
||||||
|
|
||||||
|
assert(pause_menu.handle_escape())
|
||||||
|
while pause_menu.visible:
|
||||||
|
await process_frame
|
||||||
|
assert(player.is_movement_enabled())
|
||||||
|
|
||||||
|
# Fishing can complete while pause owns local input. Closing pause must only
|
||||||
|
# release its own lock, not restore the stale movement value from open time.
|
||||||
|
player.set_movement_enabled(false)
|
||||||
|
pause_menu.open_menu()
|
||||||
|
while bool(pause_menu.get("_root_transition_active")):
|
||||||
|
await process_frame
|
||||||
|
assert(not bool(player.call("_is_movement_input_enabled")))
|
||||||
|
player.set_movement_enabled(true)
|
||||||
|
assert(player.is_movement_enabled())
|
||||||
|
assert(not bool(player.call("_is_movement_input_enabled")))
|
||||||
|
pause_menu.close_menu(PauseMenu.CloseReason.USER_RETURN)
|
||||||
|
assert(player.is_movement_enabled())
|
||||||
|
assert(bool(player.call("_is_movement_input_enabled")))
|
||||||
|
|
||||||
|
# Opening pause during a right-click camera drag must not snapshot and later
|
||||||
|
# restore the drag's transient captured mouse mode after the drag is cancelled.
|
||||||
|
assert(
|
||||||
|
pause_menu.call(
|
||||||
|
"_get_restorable_mouse_mode",
|
||||||
|
Input.MOUSE_MODE_CAPTURED,
|
||||||
|
) == Input.MOUSE_MODE_VISIBLE
|
||||||
|
)
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
player.call("_set_camera_dragging", true)
|
||||||
|
assert(bool(player.get("_camera_dragging")))
|
||||||
|
pause_menu.open_menu()
|
||||||
|
while bool(pause_menu.get("_root_transition_active")):
|
||||||
|
await process_frame
|
||||||
|
assert(not bool(player.get("_camera_dragging")))
|
||||||
|
assert(Input.mouse_mode == Input.MOUSE_MODE_VISIBLE)
|
||||||
|
pause_menu.close_menu(PauseMenu.CloseReason.USER_RETURN)
|
||||||
|
assert(Input.mouse_mode == Input.MOUSE_MODE_VISIBLE)
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,7 @@ var _settings_manager: SettingsManagerType
|
||||||
var _fishing_spot: FishingSpotType
|
var _fishing_spot: FishingSpotType
|
||||||
var _network_session: NetworkSessionType
|
var _network_session: NetworkSessionType
|
||||||
var _saved_servers: SavedServerStoreType
|
var _saved_servers: SavedServerStoreType
|
||||||
var _prior_movement_enabled: bool = true
|
|
||||||
var _prior_camera_enabled: bool = true
|
|
||||||
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||||
var _control_snapshot_stored: bool = false
|
|
||||||
var _mouse_snapshot_stored: bool = false
|
var _mouse_snapshot_stored: bool = false
|
||||||
var _confirmation_action: ConfirmationAction = ConfirmationAction.NONE
|
var _confirmation_action: ConfirmationAction = ConfirmationAction.NONE
|
||||||
var _action_in_progress: bool = false
|
var _action_in_progress: bool = false
|
||||||
|
|
@ -128,13 +125,11 @@ func setup(
|
||||||
func open_menu() -> void:
|
func open_menu() -> void:
|
||||||
if visible or _player == null:
|
if visible or _player == null:
|
||||||
return
|
return
|
||||||
_prior_movement_enabled = _player.is_movement_enabled()
|
# Suppression ends an active right-click camera drag and restores its prior
|
||||||
_prior_camera_enabled = _player.is_camera_input_enabled()
|
# mouse mode. Snapshot only after that transient capture has been released.
|
||||||
_prior_mouse_mode = Input.mouse_mode
|
_player.set_local_input_suppressed(INPUT_OWNER, true)
|
||||||
_control_snapshot_stored = true
|
_prior_mouse_mode = _get_restorable_mouse_mode(Input.mouse_mode)
|
||||||
_mouse_snapshot_stored = true
|
_mouse_snapshot_stored = true
|
||||||
_player.set_movement_enabled(false)
|
|
||||||
_player.set_camera_input_enabled(false)
|
|
||||||
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
|
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
_feedback.text = ""
|
_feedback.text = ""
|
||||||
|
|
@ -452,8 +447,9 @@ func report_reset_failure() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _begin_root_entry(flurry_already_emitted: bool) -> void:
|
func _begin_root_entry(flurry_already_emitted: bool) -> void:
|
||||||
if _root_transition_active:
|
# Child pages become interactive before the root page has completely left.
|
||||||
return
|
# If one closes during that overlap, reverse the active root transition
|
||||||
|
# instead of dropping the navigation request and leaving every page hidden.
|
||||||
_root_transition_generation += 1
|
_root_transition_generation += 1
|
||||||
_root_transition_active = true
|
_root_transition_active = true
|
||||||
if not flurry_already_emitted:
|
if not flurry_already_emitted:
|
||||||
|
|
@ -530,15 +526,20 @@ func _finish_close(reason: CloseReason, restore_controls: bool) -> void:
|
||||||
_action_in_progress = false
|
_action_in_progress = false
|
||||||
_transition_flurry.clear_flurries()
|
_transition_flurry.clear_flurries()
|
||||||
hide()
|
hide()
|
||||||
|
if _player != null and is_instance_valid(_player):
|
||||||
|
# Pause owns only its input suppression. Gameplay systems such as fishing
|
||||||
|
# remain free to change their underlying movement state while it is open.
|
||||||
|
_player.set_local_input_suppressed(INPUT_OWNER, false)
|
||||||
|
if not restore_controls:
|
||||||
|
# The caller is transferring control to title/session or recovery logic.
|
||||||
|
# Preserve the previous no-input handoff until that system takes over.
|
||||||
|
_player.set_movement_enabled(false)
|
||||||
|
_player.set_camera_input_enabled(false)
|
||||||
if _fishing_spot != null and is_instance_valid(_fishing_spot):
|
if _fishing_spot != null and is_instance_valid(_fishing_spot):
|
||||||
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false)
|
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false)
|
||||||
var current_viewport: Viewport = get_viewport()
|
var current_viewport: Viewport = get_viewport()
|
||||||
if current_viewport != null:
|
if current_viewport != null:
|
||||||
current_viewport.gui_release_focus()
|
current_viewport.gui_release_focus()
|
||||||
if restore_controls:
|
|
||||||
_restore_controls()
|
|
||||||
else:
|
|
||||||
_control_snapshot_stored = false
|
|
||||||
_apply_mouse_close_policy(reason)
|
_apply_mouse_close_policy(reason)
|
||||||
menu_visibility_changed.emit(false)
|
menu_visibility_changed.emit(false)
|
||||||
|
|
||||||
|
|
@ -555,13 +556,10 @@ func _update_responsive_pause_stage() -> void:
|
||||||
_presentation_scale_root.position = Vector2.ZERO
|
_presentation_scale_root.position = Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
func _restore_controls() -> void:
|
func _get_restorable_mouse_mode(mouse_mode: Input.MouseMode) -> Input.MouseMode:
|
||||||
if not _control_snapshot_stored:
|
if mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||||
return
|
return Input.MOUSE_MODE_VISIBLE
|
||||||
if _player != null and is_instance_valid(_player):
|
return mouse_mode
|
||||||
_player.set_movement_enabled(_prior_movement_enabled)
|
|
||||||
_player.set_camera_input_enabled(_prior_camera_enabled)
|
|
||||||
_control_snapshot_stored = false
|
|
||||||
|
|
||||||
|
|
||||||
func _apply_mouse_close_policy(reason: CloseReason) -> void:
|
func _apply_mouse_close_policy(reason: CloseReason) -> void:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue