using System.Runtime.ExceptionServices; namespace AcDream.App.Rendering; /// /// Runs a texture mutation with the caller's prior binding restored on every /// exit path. The concrete GL adapter supplies checked commands; tests can /// fault each edge without requiring a graphics context. /// internal sealed class RestoredTextureBindingMutation { private uint? _pendingRestore; private bool _operationActive; internal bool HasPendingRestore => _pendingRestore.HasValue; public void Execute( Func readBinding, Action bind, uint mutationBinding, Action mutation) { ArgumentNullException.ThrowIfNull(readBinding); ArgumentNullException.ThrowIfNull(bind); ArgumentNullException.ThrowIfNull(mutation); if (_operationActive) throw new InvalidOperationException("A texture-binding mutation is already active."); _operationActive = true; try { ExecuteCore(readBinding, bind, mutationBinding, mutation); } finally { _operationActive = false; } } private void ExecuteCore( Func readBinding, Action bind, uint mutationBinding, Action mutation) { if (_pendingRestore is uint pending) { bind(pending); _pendingRestore = null; } uint previousBinding = readBinding(); _pendingRestore = previousBinding; Exception? mutationFailure = null; try { bind(mutationBinding); mutation(); } catch (Exception failure) { mutationFailure = failure; } try { bind(previousBinding); _pendingRestore = null; } catch (Exception restoreFailure) { if (mutationFailure is not null) throw new AggregateException( "Texture mutation and binding restoration both failed.", mutationFailure, restoreFailure); throw; } if (mutationFailure is not null) ExceptionDispatchInfo.Capture(mutationFailure).Throw(); } }