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 > ( ) ;
2021-03-20 15:13:39 +01:00
private readonly Dictionary < Event , List < ActiveCoroutine > > eventCoroutines = new Dictionary < Event , List < ActiveCoroutine > > ( ) ;
2021-04-08 19:31:11 +02:00
private readonly HashSet < ( Event , ActiveCoroutine ) > eventCoroutinesToRemove = new HashSet < ( Event , ActiveCoroutine ) > ( ) ;
2021-03-21 01:58:16 +01:00
private readonly HashSet < ActiveCoroutine > outstandingEventCoroutines = new HashSet < ActiveCoroutine > ( ) ;
private readonly HashSet < ActiveCoroutine > outstandingTickingCoroutines = new HashSet < ActiveCoroutine > ( ) ;
2020-12-20 17:12:43 +01:00
private readonly Stopwatch stopwatch = new Stopwatch ( ) ;
2021-11-29 11:37:17 +01:00
private readonly object lockObject = new object ( ) ;
2020-12-20 17:12:43 +01:00
/// <summary>
/// The amount of <see cref="ActiveCoroutine"/> instances that are currently waiting for a tick (waiting for time to pass)
/// </summary>
2021-11-29 11:37:17 +01:00
public int TickingCount {
get {
lock ( this . lockObject )
return this . tickingCoroutines . Count ;
}
}
2020-12-20 17:12:43 +01:00
/// <summary>
/// The amount of <see cref="ActiveCoroutine"/> instances that are currently waiting for an <see cref="Event"/>
/// </summary>
2021-11-29 11:37:17 +01:00
public int EventCount {
get {
lock ( this . lockObject )
return this . eventCoroutines . Sum ( c = > c . Value . 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 ) ;
2021-11-29 11:37:17 +01:00
if ( inst . MoveNext ( ) ) {
lock ( this . lockObject )
this . GetOutstandingCoroutines ( inst . IsWaitingForEvent ) . Add ( 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 ) {
2023-02-23 19:04:16 +01:00
return this . Start ( CoroutineHandlerInstance . InvokeLaterImpl ( wait , action ) , name , priority ) ;
2020-06-13 03:02:11 +02:00
}
2023-02-23 19:08:26 +01:00
/// <summary>
/// Causes the given action to be invoked after the given <see cref="Event"/>.
/// This is equivalent to a coroutine that waits for the given wait and then executes the given <see cref="Action"/>.
/// </summary>
/// <param name="evt">The event to wait for</param>
/// <param name="action">The action to execute after waiting</param>
/// <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>
/// <returns>An active coroutine object representing this coroutine</returns>
public ActiveCoroutine InvokeLater ( Event evt , Action action , string name = "" , int priority = 0 ) {
return this . InvokeLater ( new Wait ( evt ) , action , name , priority ) ;
}
2020-06-13 03:12:26 +02:00
/// <summary>
/// Ticks this coroutine handler, causing all time-based <see cref="Wait"/>s to be ticked.
/// </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-11-29 11:37:17 +01:00
lock ( this . lockObject ) {
this . MoveOutstandingCoroutines ( false ) ;
this . tickingCoroutines . RemoveAll ( c = > {
if ( c . Tick ( deltaSeconds ) ) {
return true ;
} else if ( c . IsWaitingForEvent ) {
this . outstandingEventCoroutines . Add ( c ) ;
return true ;
}
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-11-29 11:37:17 +01:00
lock ( this . lockObject ) {
this . MoveOutstandingCoroutines ( true ) ;
var coroutines = this . GetEventCoroutines ( evt , false ) ;
if ( coroutines ! = null ) {
for ( var i = 0 ; i < coroutines . Count ; i + + ) {
var c = coroutines [ i ] ;
var tup = ( c . Event , c ) ;
if ( this . eventCoroutinesToRemove . Contains ( tup ) )
continue ;
if ( c . OnEvent ( evt ) ) {
this . eventCoroutinesToRemove . Add ( tup ) ;
} else if ( ! c . IsWaitingForEvent ) {
this . eventCoroutinesToRemove . Add ( tup ) ;
this . outstandingTickingCoroutines . Add ( c ) ;
}
2021-03-20 15:13:39 +01:00
}
2020-06-13 03:02:11 +02:00
}
2021-03-19 18:37:17 -04: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 ( ) {
2021-11-29 11:37:17 +01:00
lock ( this . lockObject )
return this . tickingCoroutines . Concat ( this . eventCoroutines . Values . SelectMany ( c = > c ) ) ;
2020-06-13 03:02:11 +02:00
}
2021-03-20 21:24:18 +01:00
private void MoveOutstandingCoroutines ( bool evt ) {
2021-03-20 15:24:25 +01:00
// RemoveWhere is twice as fast as iterating and then clearing
2021-03-20 21:24:18 +01:00
if ( this . eventCoroutinesToRemove . Count > 0 ) {
this . eventCoroutinesToRemove . RemoveWhere ( c = > {
2021-04-08 19:31:11 +02:00
this . GetEventCoroutines ( c . Item1 , false ) . Remove ( c . Item2 ) ;
2021-03-20 21:24:18 +01:00
return true ;
} ) ;
}
2021-03-21 01:58:16 +01:00
var coroutines = this . GetOutstandingCoroutines ( evt ) ;
if ( coroutines . Count > 0 ) {
coroutines . RemoveWhere ( c = > {
2021-03-21 02:02:11 +01:00
var list = evt ? this . GetEventCoroutines ( c . Event , true ) : this . tickingCoroutines ;
2021-03-20 21:24:18 +01:00
var position = list . BinarySearch ( c ) ;
list . Insert ( position < 0 ? ~ position : position , c ) ;
return true ;
} ) ;
}
2021-03-20 15:13:39 +01:00
}
2021-03-21 01:58:16 +01:00
private HashSet < ActiveCoroutine > GetOutstandingCoroutines ( bool evt ) {
return evt ? this . outstandingEventCoroutines : this . outstandingTickingCoroutines ;
}
2021-03-20 21:21:05 +01:00
private List < ActiveCoroutine > GetEventCoroutines ( Event evt , bool create ) {
if ( ! this . eventCoroutines . TryGetValue ( evt , out var ret ) & & create ) {
ret = new List < ActiveCoroutine > ( ) ;
2021-03-20 15:13:39 +01:00
this . eventCoroutines . Add ( evt , ret ) ;
2021-03-17 01:13:57 +01:00
}
2021-03-20 15:13:39 +01:00
return ret ;
2021-03-17 01:13:57 +01:00
}
2020-06-13 03:02:11 +02:00
private static IEnumerator < Wait > InvokeLaterImpl ( Wait wait , Action action ) {
yield return wait ;
action ( ) ;
}
}
}