< Summary

Information
Class: Gravitas.Support.LSCoroutine
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Support/Coroutines/LSCoroutine.cs
Line coverage
100%
Covered lines: 69
Uncovered lines: 0
Coverable lines: 69
Total lines: 157
Line coverage: 100%
Branch coverage
100%
Covered branches: 32
Total branches: 32
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Simulate()100%66100%
SimulateCore()100%1414100%
End()100%22100%
DisposeResources()100%88100%
DisposeCurrentInstruction()100%22100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Support/Coroutines/LSCoroutine.cs

#LineLine coverage
 1//=======================================================================
 2// LSCoroutine.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Collections.Generic;
 10using System.Runtime.ExceptionServices;
 11
 12namespace Gravitas.Support;
 13
 14/// <summary>
 15/// Represents one context-owned lockstep coroutine.
 16/// </summary>
 17public sealed class LSCoroutine
 18{
 19    private readonly IEnumerator<ILockedYieldInstruction> _enumerator;
 20    private ILockedYieldInstruction? _currentInstruction;
 21    // Cancellation marks Active immediately, but disposal waits until the current user callback unwinds.
 22    private bool _executing;
 23
 7624    internal LSCoroutine(GravitasCoroutineService owner, IEnumerator<ILockedYieldInstruction> enumerator)
 25    {
 7626        Owner = owner;
 7627        _enumerator = enumerator;
 7628    }
 29
 30    /// <summary>
 31    /// Gets whether this coroutine is still active.
 32    /// </summary>
 33    public bool Active { get; private set; } = true;
 34
 35    internal GravitasCoroutineService Owner { get; }
 36
 37    internal int Index { get; set; } = -1;
 38
 39    internal void Simulate()
 40    {
 4841        Exception? executionException = null;
 4842        _executing = true;
 43        try
 44        {
 4845            SimulateCore();
 4346        }
 47        catch (Exception exception)
 48        {
 549            executionException = exception;
 550        }
 51        finally
 52        {
 4853            _executing = false;
 4854            if (!Active)
 55            {
 56                try
 57                {
 1558                    DisposeResources();
 1359                }
 260                catch (Exception cleanupException)
 61                {
 262                    if (executionException != null)
 163                        throw new AggregateException(executionException, cleanupException);
 64
 165                    throw;
 66                }
 67            }
 4668        }
 69
 4670        if (executionException != null)
 471            ExceptionDispatchInfo.Capture(executionException).Throw();
 4272    }
 73
 74    private void SimulateCore()
 75    {
 4876        if (_currentInstruction != null)
 77        {
 1278            bool keepWaiting = _currentInstruction.KeepWaiting;
 1179            if (!Active || keepWaiting)
 380                return;
 81
 882            DisposeCurrentInstruction();
 883            if (!Active)
 184                return;
 85        }
 86
 4387        if (!_enumerator.MoveNext())
 88        {
 789            Owner.StopCoroutine(this);
 790            return;
 91        }
 92
 3393        ILockedYieldInstruction? nextInstruction = _enumerator.Current;
 3394        _currentInstruction = nextInstruction;
 3395        if (!Active)
 596            return;
 97
 2898        if (_currentInstruction != null
 2899            && !ReferenceEquals(_currentInstruction.Context, Owner.Context))
 100        {
 1101            throw new InvalidOperationException(
 1102                "Coroutine yield instructions must belong to the coroutine service context.");
 103        }
 27104    }
 105
 106    internal void End()
 107    {
 76108        Active = false;
 76109        if (!_executing)
 61110            DisposeResources();
 73111    }
 112
 113    private void DisposeResources()
 114    {
 76115        ILockedYieldInstruction? instruction = _currentInstruction;
 76116        _currentInstruction = null;
 117
 76118        if (ReferenceEquals(instruction, _enumerator))
 119        {
 2120            _enumerator.Dispose();
 2121            return;
 122        }
 123
 74124        Exception? instructionException = null;
 125        try
 126        {
 74127            instruction?.Dispose();
 72128        }
 129        catch (Exception exception)
 130        {
 2131            instructionException = exception;
 2132        }
 133
 134        try
 135        {
 74136            _enumerator.Dispose();
 70137        }
 4138        catch (Exception enumeratorException)
 139        {
 4140            if (instructionException != null)
 1141                throw new AggregateException(instructionException, enumeratorException);
 142
 3143            throw;
 144        }
 145
 70146        if (instructionException != null)
 1147            ExceptionDispatchInfo.Capture(instructionException).Throw();
 69148    }
 149
 150    private void DisposeCurrentInstruction()
 151    {
 8152        ILockedYieldInstruction instruction = _currentInstruction!;
 8153        _currentInstruction = null;
 8154        if (!ReferenceEquals(instruction, _enumerator))
 7155            instruction.Dispose();
 8156    }
 157}