Editor utility that manages one active modal transform operation per SceneEditorSession. It stores per-session SessionState in a ConditionalWeakTable, starts/aborts operations, forwards per-frame Tick calls, and clears state on hotload.
#nullable enable
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace BlenderActions;
/// <summary>Coordinates one active modal transform operation per editor session.</summary>
public static class ModalOperationArbiter
{
/// <summary>Handles new.</summary>
private static ConditionalWeakTable<SceneEditorSession, SessionState> _states = new();
/// <summary>Handles new.</summary>
private static readonly List<WeakReference<SessionState>> StateReferences = new();
/// <summary>Returns state for the active editor session and optionally creates it.</summary>
public static ModalTransformOperation? Active => GetCurrentState(false)?.Active;
/// <summary>Gets numeric input associated with the current modal operation.</summary>
public static NumericInputSession? NumericInput => Active?.NumericInput;
/// <summary>Starts an operation for the active editor session when no operation is already running.</summary>
public static void Start(ModalTransformOperation operation)
{
var session = SceneEditorSession.Active;
if(session == null)
return;
var state = GetState(session);
if(state.Active != null)
return;
state.Active = operation;
try
{
if(!operation.Begin(session))
state.Active = null;
}
catch
{
state.Active = null;
throw;
}
}
/// <summary>Applies or toggles an axis constraint on the active operation.</summary>
public static void SetConstraint(AxisConstraint constraint)
{
GetCurrentState(false)?.Active?.SetConstraint(constraint);
}
/// <summary>Cancels the active operation for the current editor session.</summary>
public static void Cancel()
{
GetCurrentState(false)?.Active?.Abort();
}
/// <summary>Releases an operation from its bound session state.</summary>
internal static void Release(ModalTransformOperation operation)
{
var session = operation.BoundSession;
if(session == null || !_states.TryGetValue(session, out var state))
return;
if(ReferenceEquals(state.Active, operation))
state.Active = null;
}
/// <summary>Advances active modal operations once per editor tool frame.</summary>
[Event("tool.frame")]
private static void OnToolFrame()
{
for(var index = StateReferences.Count - 1; index >= 0; index--)
{
if(!StateReferences[index].TryGetTarget(out var state))
{
StateReferences.RemoveAt(index);
continue;
}
state.Active?.Tick();
}
}
/// <summary>Aborts active operations and resets session state after hotload.</summary>
[EditorEvent.Hotload]
private static void OnHotload()
{
for(var index = StateReferences.Count - 1; index >= 0; index--)
{
if(StateReferences[index].TryGetTarget(out var state))
state.Active?.Abort();
}
StateReferences.Clear();
_states = new ConditionalWeakTable<SceneEditorSession, SessionState>();
}
/// <summary>Returns state for the active editor session and optionally creates it.</summary>
private static SessionState? GetCurrentState(bool create)
{
var session = SceneEditorSession.Active;
if(session == null)
return null;
if(create)
return GetState(session);
return _states.TryGetValue(session, out var state)
? state
: null;
}
/// <summary>Returns or creates modal state for a specific editor session.</summary>
private static SessionState GetState(SceneEditorSession session)
{
if(_states.TryGetValue(session, out var state))
return state;
state = new SessionState();
_states.Add(session, state);
StateReferences.Add(new WeakReference<SessionState>(state));
return state;
}
/// <summary>Stores modal operation state associated with one editor session.</summary>
private sealed class SessionState
{
/// <summary>Gets or sets the active modal operation for this session.</summary>
public ModalTransformOperation? Active { get; set; }
}
}