Restore separate GameEditorSessions (#3453)

* SceneEditorSession: make game vs editor scenes more distinct, Scene is whichever is currently active

* Route prefab update, model reload etc events to both scenes if needed

* SceneTree update checking a bit cleaner

* Bring back GameEditorSessions instead, so undo, selection etc can all be linked 1:1 with scenes again

* tweak and tidy
This commit is contained in:
Sol Williams
2025-11-26 16:05:23 +00:00
committed by GitHub
parent 94d8c22b84
commit 80aa601771
14 changed files with 103 additions and 71 deletions

View File

@@ -295,7 +295,7 @@ public static class EditorScene
public static void Stop()
{
SceneEditorSession.ActiveGameSession.StopPlaying();
SceneEditorSession.Active.StopPlaying();
Game.IsPlaying = false;

View File

@@ -0,0 +1,27 @@
namespace Editor;
public class GameEditorSession : SceneEditorSession
{
internal static GameEditorSession Current = null;
public SceneEditorSession Parent { get; init; }
public override bool IsPlaying => true;
public GameEditorSession( SceneEditorSession parent, Scene scene ) : base( scene )
{
Parent = parent;
Assert.IsNull( Current, "Attempted to create new GameEditorSession when one already exists!" );
Current = this;
}
public override void Destroy()
{
base.Destroy();
Current = null;
}
public override void StopPlaying() => Parent.StopPlaying();
}

View File

@@ -2,22 +2,24 @@
partial class SceneEditorSession
{
public Scene ActiveGameScene => activeGameScene;
public bool HasActiveGameScene => activeGameScene != null;
/// <summary>
/// The game session of this editor session, if playing.
/// </summary>
public GameEditorSession GameSession { get; private set; }
public static SceneEditorSession ActiveGameSession => All.Where( s => s.HasActiveGameScene ).FirstOrDefault();
Scene activeGameScene;
public virtual bool IsPlaying => GameSession != null;
public void SetPlaying( Scene scene )
{
activeGameScene = scene;
activeGameScene.Editor = this;
GameSession = new GameEditorSession( this, scene );
GameSession.MakeActive();
}
public void StopPlaying()
public virtual void StopPlaying()
{
activeGameScene?.Destroy();
activeGameScene = null;
GameSession?.Destroy();
GameSession = null;
MakeActive();
}
}

View File

@@ -15,20 +15,18 @@ public partial class SceneEditorSession : Scene.ISceneEditorSession
/// </summary>
public static List<SceneEditorSession> All { get; } = new();
private static SceneEditorSession _active;
/// <summary>
/// The editor session that is currently active
/// </summary>
public static SceneEditorSession Active
{
get => _active;
get;
private set
{
if ( _active == value ) return;
if ( field == value ) return;
_active = value;
_active?.UpdateEditorTitle();
field = value;
field?.UpdateEditorTitle();
}
}
@@ -41,6 +39,9 @@ public partial class SceneEditorSession : Scene.ISceneEditorSession
/// </summary>
public bool IsPrefabSession => this is PrefabEditorSession;
/// <summary>
/// The scene for this session
/// </summary>
public Scene Scene { get; private set; }
internal Widget SceneDock { get; set; }
@@ -57,7 +58,11 @@ public partial class SceneEditorSession : Scene.ISceneEditorSession
InitUndo();
timeSinceSavedState = 0;
CreateSceneDock();
if ( this is not GameEditorSession )
{
// create dock - but not for game sessions, those are built into the parent session widget
CreateSceneDock();
}
EditorEvent.Register( this );
}
@@ -120,7 +125,7 @@ public partial class SceneEditorSession : Scene.ISceneEditorSession
bool _destroyed;
public void Destroy()
public virtual void Destroy()
{
if ( _destroyed )
return;
@@ -149,6 +154,9 @@ public partial class SceneEditorSession : Scene.ISceneEditorSession
Scene?.Destroy();
Scene = null;
GameSession?.Destroy();
GameSession = null;
SceneDock?.Destroy();
SceneDock = default;
}
@@ -212,18 +220,6 @@ public partial class SceneEditorSession : Scene.ISceneEditorSession
else
{
SceneDock.SetWindowIcon( "grid_4x4" );
//
// Change the title to reflect current mode
//
if ( HasActiveGameScene )
{
SceneDock.WindowTitle = $"{title} (active)";
}
else
{
SceneDock.WindowTitle = title;
}
}
}
@@ -264,7 +260,7 @@ public partial class SceneEditorSession : Scene.ISceneEditorSession
}
/// <summary>
/// Pushes the active editor scene to the current scope
/// Pushes the active scene to the current scope
/// </summary>
public static IDisposable Scope()
{
@@ -383,7 +379,7 @@ public partial class SceneEditorSession : Scene.ISceneEditorSession
/// </summary>
public static SceneEditorSession Resolve( Scene scene )
{
return All.FirstOrDefault( x => x.ActiveGameScene == scene || x.Scene == scene );
return All.FirstOrDefault( x => x.Scene == scene );
}
/// <summary>

View File

@@ -312,7 +312,7 @@ class ControlSheetRow : Widget
return;
// Only show "Apply To Scene" if we're editing in an active game session
if ( !session.HasActiveGameScene || component.GameObject.Scene != session.ActiveGameScene )
if ( !session.IsPlaying || component.GameObject.Scene != session.Scene )
return;
// try to find the version of this component in the editor session

View File

@@ -90,7 +90,7 @@ public partial class SceneTreeWidget : Widget
Layout.Add( TreeView, 1 );
_lastSession = null;
_lastScene.SetTarget( null );
CheckForChanges();
EditorUtility.OnInspect -= OnInspect;
@@ -125,32 +125,31 @@ public partial class SceneTreeWidget : Widget
}
}
SceneEditorSession _lastSession;
WeakReference<Scene> _lastScene = new( null );
bool queryDirty = false;
bool hasActiveGameScene = false;
[EditorEvent.Frame]
public void CheckForChanges()
{
var activeScene = SceneEditorSession.Active;
if ( activeScene is null )
var session = SceneEditorSession.Active;
if ( session is null )
return;
if ( hasActiveGameScene == activeScene.HasActiveGameScene )
{
if ( !queryDirty && _lastSession == activeScene )
return;
}
_lastScene.TryGetTarget( out var last );
// if query AND scene is unchanged - no need to rebuild the tree
if ( !queryDirty && ReferenceEquals( last, session.Scene ) )
return;
_lastScene.SetTarget( session.Scene );
_lastSession = activeScene;
queryDirty = false;
hasActiveGameScene = _lastSession.HasActiveGameScene;
Rebuild();
}
private void Rebuild()
{
var activeScene = SceneEditorSession.Active;
var session = SceneEditorSession.Active;
Header.Clear( true );
@@ -161,14 +160,13 @@ public partial class SceneTreeWidget : Widget
TreeView.Selection = new SelectionSystem();
TreeView.Clear();
if ( _lastSession is null )
if ( session is null )
return;
bool hasSearch = !string.IsNullOrEmpty( Search.Text );
SearchClear.Visible = hasSearch;
var scene = _lastSession.HasActiveGameScene ? _lastSession.ActiveGameScene : _lastSession.Scene;
var scene = session.Scene;
if ( hasSearch )
{
// flat search view
@@ -237,7 +235,7 @@ public partial class SceneTreeWidget : Widget
}
}
TreeView.Selection = activeScene.Selection;
TreeView.Selection = session.Selection;
// Go through the current scene
// Feel like this could be loads faster

View File

@@ -6,13 +6,15 @@
/// the dock is hovered or focused. It also destroys the session when the dock
/// is closed.
/// </summary>
/// Sol: does this need to exist? can't we just dock the view widget directly?
public partial class SceneDock : Widget
{
SceneEditorSession Session { get; set; }
public SceneEditorSession Session => _editorSession.GameSession ?? _editorSession;
private SceneEditorSession _editorSession;
public SceneDock( SceneEditorSession session ) : base( null )
{
Session = session;
_editorSession = session;
Layout = Layout.Row();
Layout.Add( new SceneViewWidget( session, this ) );
@@ -23,12 +25,12 @@ public partial class SceneDock : Widget
protected override bool OnClose()
{
if ( Session.HasUnsavedChanges )
if ( _editorSession.HasUnsavedChanges )
{
this.ShowUnsavedChangesDialog(
assetName: Session.Scene.Name,
assetType: Session.IsPrefabSession ? "prefab" : "scene",
onSave: () => Session.Save( false ) );
assetName: _editorSession.Scene.Name,
assetType: _editorSession.IsPrefabSession ? "prefab" : "scene",
onSave: () => _editorSession.Save( false ) );
return false;
}
@@ -40,8 +42,8 @@ public partial class SceneDock : Widget
{
base.OnDestroyed();
Session.Destroy();
Session = null;
_editorSession.Destroy();
_editorSession = null;
}
protected override void OnVisibilityChanged( bool visible )

View File

@@ -17,7 +17,7 @@ public partial class SceneViewWidget
[Event( "scene.play" )]
public void OnScenePlay()
{
if ( !Session.HasActiveGameScene ) return;
if ( !Session.IsPlaying ) return;
CurrentView = ViewMode.Game;
OnViewModeChanged();
@@ -42,7 +42,7 @@ public partial class SceneViewWidget
public void ToggleEject()
{
if ( !Session.HasActiveGameScene ) return;
if ( !Session.IsPlaying ) return;
CurrentView = CurrentView == ViewMode.Game ? ViewMode.GameEjected : ViewMode.Game;
@@ -59,12 +59,17 @@ public partial class SceneViewWidget
}
/// <summary>
/// Current view mode changed, we need to hide or show some UI things.
/// Current view mode changed
/// </summary>
void OnViewModeChanged()
{
_viewportTools.Rebuild();
_sidePanel?.Visible = CurrentView != ViewMode.Game;
foreach ( var viewport in _viewports.Values )
{
viewport.GizmoInstance.Selection = Session.Selection;
}
}
public SceneViewportWidget GetGameTarget()

View File

@@ -35,7 +35,11 @@ public partial class SceneViewWidget : Widget
public EditorToolManager Tools { get; private set; }
public SceneEditorSession Session { get; private set; }
/// <summary>
/// The currently active session for this scene view. The game session if playing, otherwise the editor session.
/// </summary>
public SceneEditorSession Session => _editorSession.GameSession ?? _editorSession;
private SceneEditorSession _editorSession;
private List<LinkableSplitter> _splitters;
public Dictionary<int, SceneViewportWidget> _viewports;
@@ -44,7 +48,7 @@ public partial class SceneViewWidget : Widget
public SceneViewWidget( SceneEditorSession session, Widget parent ) : base( parent )
{
Session = session;
_editorSession = session;
Tools = new EditorToolManager();
Layout = Layout.Column();

View File

@@ -5,7 +5,7 @@ public partial class SceneViewportWidget
public void StartPlay()
{
Editor.GameMode.SetPlayWidget( Renderer );
Renderer.Scene = Session.ActiveGameScene;
Renderer.Scene = Session.Scene;
Renderer.Camera = null;
Renderer.EnableEngineOverlays = true;
ViewportOptions.Visible = false;

View File

@@ -170,7 +170,6 @@ public partial class SceneViewportWidget
[Event( "scene.session.save" )]
public void SaveState()
{
// Don't save the state of play sessions
if ( ProjectCookie is null ) return;
Scene scene = Session.Scene;

View File

@@ -18,7 +18,7 @@ public partial class SceneViewportWidget : Widget
private float TargetFOV { get; set; } = 80;
private static float TransitionSpeed => 40;
SceneEditorSession Session => SceneView.Session;
protected virtual SceneEditorSession Session => SceneView.Session;
EditorToolManager Tools => SceneView.Tools;
public SceneRenderingWidget Renderer;
@@ -514,7 +514,6 @@ public partial class SceneViewportWidget : Widget
Tools.Frame( _activeCamera, Session );
EditorEvent.RunInterface<EditorEvent.ISceneView>( x => x.DrawGizmos( Session.Scene ) );
Session.Scene.EditorDraw();
DrawSelection();

View File

@@ -57,7 +57,7 @@ partial class ViewportTools
o.SetIcon( icon );
o.Checkable = true;
o.Checked = sceneViewWidget.ViewportLayout == layout;
o.Enabled = !sceneViewWidget.Session.HasActiveGameScene; // Not great, but this stuff needs straightening out overall
o.Enabled = !sceneViewWidget.Session.IsPlaying; // Not great, but this stuff needs straightening out overall
}
menu.OpenAtCursor();

View File

@@ -305,7 +305,7 @@ public partial class MovieEditor : Widget, IHotloadManaged
void UpdateEditorContext()
{
var activeEditorSession = SceneEditorSession.Active;
var activeScene = activeEditorSession?.ActiveGameScene ?? activeEditorSession?.Scene;
var activeScene = activeEditorSession?.Scene;
// The current session exists
if ( Session is { } session )
@@ -367,7 +367,7 @@ public partial class MovieEditor : Widget, IHotloadManaged
public void CreateNewPlayer()
{
using ( SceneEditorSession.Active.Scene.Push() )
using ( SceneEditorSession.Scope() )
{
var go = new GameObject( true, "New Movie Player" );
go.Components.Create<MoviePlayer>();