Editor/ModalOperationArbiter.cs

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.

Reflection
#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; }
    }
}