// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System;
using System.Threading;
using FlaxEditor.States;
using FlaxEditor.Windows;
using FlaxEngine;
namespace FlaxEditor.Modules
{
///
/// Manages play in-editor feature (game simulation).
///
///
public sealed class SimulationModule : EditorModule
{
private bool _isPlayModeRequested;
private bool _isPlayModeStopRequested;
private bool _stepFrame;
private bool _updateOrFixedUpdateWasCalled;
private long _breakpointHangFlag;
private EditorWindow _enterPlayFocusedWindow;
internal SimulationModule(Editor editor)
: base(editor)
{
FlaxEngine.Scripting.FixedUpdate += OnFixedUpdate;
}
///
/// Gets a value indicating whether Editor is during breakpoint hit hang (eg. Visual Script breakpoint).
///
public bool IsDuringBreakpointHang { get; set; }
///
/// Occurs when breakpoint hang begins (on eg. Visual Script breakpoint hit).
///
public event Action BreakpointHangBegin;
///
/// Occurs when breakpoint hang ends (eg. Visual Script debugging tools continue game execution).
///
public event Action BreakpointHangEnd;
internal object BreakpointHangTag;
internal void OnBreakpointHangBegin()
{
_breakpointHangFlag = 1;
IsDuringBreakpointHang = true;
BreakpointHangBegin?.Invoke();
}
internal void StopBreakpointHang()
{
Interlocked.Decrement(ref _breakpointHangFlag);
}
internal bool ContinueBreakpointHang()
{
return Interlocked.Read(ref _breakpointHangFlag) == 1;
}
internal void OnBreakpointHangEnd()
{
IsDuringBreakpointHang = false;
BreakpointHangTag = null;
BreakpointHangEnd?.Invoke();
}
///
/// Checks if play mode should start only with single frame update and then enter step mode.
///
public bool ShouldPlayModeStartWithStep => Editor.UI.IsPauseButtonChecked;
///
/// Returns true if play mode has been requested.
///
public bool IsPlayModeRequested => _isPlayModeRequested;
///
/// Requests start playing in editor.
///
public void RequestStartPlay()
{
if (Editor.StateMachine.IsEditMode)
{
Editor.Log("[PlayMode] Start");
if (Editor.Options.Options.General.AutoReloadScriptsOnMainWindowFocus)
ScriptsBuilder.CheckForCompile();
_isPlayModeRequested = true;
Editor.UI.UpdateToolstrip();
}
}
///
/// Requests stop playing in editor.
///
public void RequestStopPlay()
{
if (Editor.StateMachine.IsPlayMode)
{
Editor.Log("[PlayMode] Stop");
if (IsDuringBreakpointHang)
StopBreakpointHang();
_isPlayModeStopRequested = true;
Editor.UI.UpdateToolstrip();
}
}
///
/// Requests the playing start or stop in editor.
///
public void RequestPlayOrStopPlay()
{
if (Editor.StateMachine.IsPlayMode)
RequestStopPlay();
else
RequestStartPlay();
}
///
/// Requests the playing mode resume or pause if already running.
///
public void RequestResumeOrPause()
{
if (Editor.StateMachine.PlayingState.IsPaused)
Editor.Simulation.RequestResumePlay();
else
Editor.Simulation.RequestPausePlay();
}
///
/// Requests pause in playing.
///
public void RequestPausePlay()
{
if (Editor.StateMachine.IsPlayMode && !Editor.StateMachine.PlayingState.IsPaused)
{
Editor.Log("[PlayMode] Pause");
Editor.StateMachine.PlayingState.IsPaused = true;
Editor.UI.UpdateToolstrip();
}
}
///
/// Request resume in playing.
///
public void RequestResumePlay()
{
if (Editor.StateMachine.IsPlayMode && Editor.StateMachine.PlayingState.IsPaused)
{
Editor.Log("[PlayMode] Resume");
Editor.StateMachine.PlayingState.IsPaused = false;
Editor.UI.UpdateToolstrip();
}
}
///
/// Requests playing single frame in advance.
///
public void RequestPlayOneFrame()
{
if (Editor.StateMachine.IsPlayMode && Editor.StateMachine.PlayingState.IsPaused)
{
Editor.Log("[PlayMode] Step one frame");
// Set flag
_stepFrame = true;
_updateOrFixedUpdateWasCalled = false;
Editor.StateMachine.PlayingState.IsPaused = false;
// Update
Editor.UI.UpdateToolstrip();
}
}
///
public override void OnPlayBegin()
{
Editor.Windows.FlashMainWindow();
// Pick focused window to restore it
var gameWin = Editor.Windows.GameWin;
var editWin = Editor.Windows.EditWin;
if (editWin != null && editWin.IsSelected)
_enterPlayFocusedWindow = editWin;
else if (gameWin != null && gameWin.IsSelected)
_enterPlayFocusedWindow = gameWin;
// Show Game widow if hidden
if (gameWin != null && gameWin.FocusOnPlay)
{
if (!gameWin.IsDocked)
{
gameWin.ShowFloating();
}
else if (!gameWin.IsSelected)
{
gameWin.SelectTab(false);
FlaxEngine.GUI.RootControl.GameRoot.Focus();
}
}
Editor.Log("[PlayMode] Enter");
}
///
public override void OnPlayEnd()
{
// Restore focused window before play mode
if (_enterPlayFocusedWindow != null)
{
_enterPlayFocusedWindow.FocusOrShow();
_enterPlayFocusedWindow = null;
}
Editor.UI.UncheckPauseButton();
Editor.Log("[PlayMode] Exit");
}
///
public override void OnUpdate()
{
// Check if can enter playing in editor mode
if (Editor.StateMachine.CurrentState.CanEnterPlayMode)
{
// Check if play mode has been requested
if (_isPlayModeRequested)
{
// Check if editor has been compiled and scripting reloaded (there is no pending reload action)
if ((ScriptsBuilder.IsReady || !Editor.Options.Options.General.AutoReloadScriptsOnMainWindowFocus) && !Level.IsAnyActionPending)
{
// Clear flag
_isPlayModeRequested = false;
// Enter play mode
Editor.StateMachine.GoToState();
// Check if move just by one frame
if (ShouldPlayModeStartWithStep)
{
RequestPausePlay();
}
}
}
// Check if play mode exit has been requested
else if (_isPlayModeStopRequested)
{
// Clear flag
_isPlayModeStopRequested = false;
// Exit play mode
Editor.StateMachine.GoToState();
}
// Check if step one frame
else if (_stepFrame)
{
if (_updateOrFixedUpdateWasCalled)
{
// Clear flag and pause
_stepFrame = false;
Editor.StateMachine.PlayingState.IsPaused = true;
Editor.UI.UpdateToolstrip();
}
else
{
// Fixed update may not be called but don't allow to call update for more than 2 times during single step
_updateOrFixedUpdateWasCalled = true;
}
}
}
else
{
// Clear flags
_isPlayModeRequested = false;
_isPlayModeStopRequested = false;
_stepFrame = false;
_updateOrFixedUpdateWasCalled = false;
}
}
private void OnFixedUpdate()
{
// Rise the flag so play mode step end will be called after physics update (user see objects movement)
_updateOrFixedUpdateWasCalled = true;
}
}
}