2020-06-13 03:02:11 +02:00
using System ;
using System.Collections.Generic ;
2020-12-20 17:12:43 +01:00
using System.Diagnostics ;
2020-06-13 03:02:11 +02:00
using System.Linq ;
namespace Coroutine {
2020-06-13 03:12:26 +02:00
/// <summary>
/// An object of this class can be used to start, tick and otherwise manage active <see cref="ActiveCoroutine"/>s as well as their <see cref="Event"/>s.
/// Note that a static implementation of this can be found in <see cref="CoroutineHandler"/>.
/// </summary>
2020-06-13 03:02:11 +02:00
public class CoroutineHandlerInstance {
private readonly List < ActiveCoroutine > tickingCoroutines = new List < ActiveCoroutine > ( ) ;
private readonly List < ActiveCoroutine > eventCoroutines = new List < ActiveCoroutine > ( ) ;
2021-03-17 01:13:57 +01:00
private readonly Queue < ActiveCoroutine > outstandingCoroutines = new Queue < ActiveCoroutine > ( ) ;
2021-03-20 01:50:00 +01:00
private readonly ISet < int > eventCoroutinesToRemove = new HashSet < int > ( ) ;
2020-12-20 17:12:43 +01:00
private readonly Stopwatch stopwatch = new Stopwatch ( ) ;
/// <summary>
/// The amount of <see cref="ActiveCoroutine"/> instances that are currently waiting for a tick (waiting for time to pass)
/// </summary>
public int TickingCount = > this . tickingCoroutines . Count ;
/// <summary>
/// The amount of <see cref="ActiveCoroutine"/> instances that are currently waiting for an <see cref="Event"/>
/// </summary>
public int EventCount = > this . eventCoroutines . Count ;
2020-06-13 03:02:11 +02:00
2020-06-13 03:12:26 +02:00
/// <summary>
/// Starts the given coroutine, returning a <see cref="ActiveCoroutine"/> object for management.
/// Note that this calls <see cref="IEnumerable{T}.GetEnumerator"/> to get the enumerator.
/// </summary>
/// <param name="coroutine">The coroutine to start</param>
2021-03-16 19:07:28 +01:00
/// <param name="name">The <see cref="ActiveCoroutine.Name"/> that this coroutine should have. Defaults to an empty string.</param>
/// <param name="priority">The <see cref="ActiveCoroutine.Priority"/> that this coroutine should have. The higher the priority, the earlier it is advanced. Defaults to 0.</param>
2020-06-13 03:12:26 +02:00
/// <returns>An active coroutine object representing this coroutine</returns>
2021-03-16 19:07:28 +01:00
public ActiveCoroutine Start ( IEnumerable < Wait > coroutine , string name = "" , int priority = 0 ) {
return this . Start ( coroutine . GetEnumerator ( ) , name , priority ) ;
2020-06-13 03:02:11 +02:00
}
2020-06-13 03:12:26 +02:00
/// <summary>
/// Starts the given coroutine, returning a <see cref="ActiveCoroutine"/> object for management.
/// </summary>
/// <param name="coroutine">The coroutine to start</param>
2021-03-16 19:07:28 +01:00
/// <param name="name">The <see cref="ActiveCoroutine.Name"/> that this coroutine should have. Defaults to an empty string.</param>
/// <param name="priority">The <see cref="ActiveCoroutine.Priority"/> that this coroutine should have. The higher the priority, the earlier it is advanced compared to other coroutines that advance around the same time. Defaults to 0.</param>
2020-06-13 03:12:26 +02:00
/// <returns>An active coroutine object representing this coroutine</returns>
2021-03-16 19:07:28 +01:00
public ActiveCoroutine Start ( IEnumerator < Wait > coroutine , string name = "" , int priority = 0 ) {
var inst = new ActiveCoroutine ( coroutine , name , priority , this . stopwatch ) ;
if ( inst . MoveNext ( ) )
2021-03-17 01:13:57 +01:00
this . outstandingCoroutines . Enqueue ( inst ) ;
2020-06-13 03:02:11 +02:00
return inst ;
}
2020-06-13 03:12:26 +02:00
/// <summary>
/// Causes the given action to be invoked after the given <see cref="Wait"/>.
/// This is equivalent to a coroutine that waits for the given wait and then executes the given <see cref="Action"/>.
/// </summary>
/// <param name="wait">The wait to wait for</param>
/// <param name="action">The action to execute after waiting</param>
2021-03-16 19:07:28 +01:00
/// <param name="name">The <see cref="ActiveCoroutine.Name"/> that the underlying coroutine should have. Defaults to an empty string.</param>
/// <param name="priority">The <see cref="ActiveCoroutine.Priority"/> that the underlying coroutine should have. The higher the priority, the earlier it is advanced compared to other coroutines that advance around the same time. Defaults to 0.</param>
2020-12-20 17:12:43 +01:00
/// <returns>An active coroutine object representing this coroutine</returns>
2021-03-16 19:07:28 +01:00
public ActiveCoroutine InvokeLater ( Wait wait , Action action , string name = "" , int priority = 0 ) {
return this . Start ( InvokeLaterImpl ( wait , action ) , name , priority ) ;
2020-06-13 03:02:11 +02:00
}
2020-06-13 03:12:26 +02:00
/// <summary>
/// Ticks this coroutine handler, causing all time-based <see cref="Wait"/>s to be ticked.
2021-03-20 01:50:00 +01:00
/// Note that this method needs to be called even if only event-based coroutines are used.
2020-06-13 03:12:26 +02:00
/// </summary>
/// <param name="deltaSeconds">The amount of seconds that have passed since the last time this method was invoked</param>
2020-06-13 03:02:11 +02:00
public void Tick ( double deltaSeconds ) {
2021-03-20 01:50:00 +01:00
this . MoveOutstandingCoroutines ( ) ;
2021-03-17 01:13:57 +01:00
this . tickingCoroutines . RemoveAll ( c = > {
if ( c . Tick ( deltaSeconds ) ) {
return true ;
} else if ( c . IsWaitingForEvent ( ) ) {
this . outstandingCoroutines . Enqueue ( c ) ;
return true ;
2020-06-13 03:02:11 +02:00
}
2021-03-17 01:13:57 +01:00
return false ;
} ) ;
2020-06-13 03:02:11 +02:00
}
2021-03-20 02:00:46 +01:00
/// <summary>
/// Ticks this coroutine handler, causing all time-based <see cref="Wait"/>s to be ticked.
/// This is a convenience method that calls <see cref="Tick(double)"/>, but accepts a <see cref="TimeSpan"/> instead of an amount of seconds.
/// </summary>
/// <param name="delta">The time that has passed since the last time this method was invoked</param>
public void Tick ( TimeSpan delta ) {
this . Tick ( delta . TotalSeconds ) ;
}
2020-06-13 03:12:26 +02:00
/// <summary>
/// Raises the given event, causing all event-based <see cref="Wait"/>s to be updated.
/// </summary>
/// <param name="evt">The event to raise</param>
2020-06-13 03:02:11 +02:00
public void RaiseEvent ( Event evt ) {
2021-03-20 00:55:32 +01:00
for ( var i = 0 ; i < this . eventCoroutines . Count ; i + + ) {
2021-03-20 03:23:04 +01:00
if ( this . eventCoroutinesToRemove . Contains ( i ) )
continue ;
2021-03-20 01:50:00 +01:00
var c = this . eventCoroutines [ i ] ;
if ( c . OnEvent ( evt ) ) {
this . eventCoroutinesToRemove . Add ( i ) ;
} else if ( ! c . IsWaitingForEvent ( ) ) {
this . outstandingCoroutines . Enqueue ( c ) ;
this . eventCoroutinesToRemove . Add ( i ) ;
2020-06-13 03:02:11 +02:00
}
2021-03-19 23:37:17 +01:00
}
2020-06-13 03:02:11 +02:00
}
2020-06-13 03:12:26 +02:00
/// <summary>
/// Returns a list of all currently active <see cref="ActiveCoroutine"/> objects under this handler.
/// </summary>
/// <returns>All active coroutines</returns>
2020-06-13 03:02:11 +02:00
public IEnumerable < ActiveCoroutine > GetActiveCoroutines ( ) {
return this . tickingCoroutines . Concat ( this . eventCoroutines ) ;
}
2021-03-20 01:50:00 +01:00
private void MoveOutstandingCoroutines ( ) {
var i = 0 ;
this . eventCoroutines . RemoveAll ( c = > this . eventCoroutinesToRemove . Contains ( i + + ) ) ;
this . eventCoroutinesToRemove . Clear ( ) ;
2021-03-17 01:13:57 +01:00
while ( this . outstandingCoroutines . Count > 0 ) {
var coroutine = this . outstandingCoroutines . Dequeue ( ) ;
var list = coroutine . IsWaitingForEvent ( ) ? this . eventCoroutines : this . tickingCoroutines ;
var position = list . BinarySearch ( coroutine ) ;
list . Insert ( position < 0 ? ~ position : position , coroutine ) ;
}
}
2020-06-13 03:02:11 +02:00
private static IEnumerator < Wait > InvokeLaterImpl ( Wait wait , Action action ) {
yield return wait ;
action ( ) ;
}
}
}