🔍 s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (13 total matches found)
facepunch.libevents / Code/SortingHelper.cs
Game library
using System.Collections.Generic;
using System.Linq;

namespace Sandbox.Events;

/// <summary>
/// Generate an ordering based on a set of first-most and last-most items, and
/// individual constraints between pairs of items. All first-most items will be
/// ordered before all last-most items, and any other items will be put in the
/// middle unless forced to be elsewhere by a constraint.
/// </summary>
internal class SortingHelper
{
	public record struct SortConstraint( int EarlierIndex, int LaterIndex )
	{
		public SortConstraint Complement => new ( LaterIndex, EarlierIndex );
	}

	private readonly int _itemCount;

	private readonly HashSet<SortConstraint> _initialConstraints = new HashSet<SortConstraint>();

	private readonly HashSet<int> _first = new HashSet<int>();
	private readonly HashSet<int> _last = new HashSet<int>();

	public SortingHelper( int itemCount )
	{
		_itemCount = itemCount;
	}

	public void AddConstraint( int earlierIndex, int laterIndex )
	{
		_initialConstraints.Add( new SortConstraint( earlierIndex, laterIndex ) );
	}

	public void AddFirst( int earlierIndex )
	{
		_first.Add( earlierIndex );
	}

	public void AddLast( int laterIndex )
	{
		_last.Add( laterIndex );
	}

	public bool Sort( List<int> result, out SortConstraint invalidConstraint )
	{
		var middle = new HashSet<int>();

		for ( var index = 0; index < _itemCount; ++index )
		{
			if ( !_first.Contains( index ) && !_last.Contains( index ) )
				middle.Add( index );
		}

		var allConstraints = new HashSet<SortConstraint>();
		var newConstraints = new Queue<SortConstraint>();
		var beforeDict = new Dictionary<int, HashSet<int>>();
		var afterDict = new Dictionary<int, HashSet<int>>();

		bool AddWorkingConstraint( int earlierIndex, int laterIndex, out SortConstraint constraint )
		{
			constraint = new SortConstraint( earlierIndex, laterIndex );

			if ( allConstraints.Contains( constraint.Complement ) )
				return false;

			if ( !allConstraints.Add( constraint ) )
				return true;

			newConstraints.Enqueue( constraint );

			if ( !beforeDict.TryGetValue( earlierIndex, out var before ) )
				beforeDict.Add( earlierIndex, before = new HashSet<int>() );

			if ( !afterDict.TryGetValue( laterIndex, out var after ) )
				afterDict.Add( laterIndex, after = new HashSet<int>() );

			before.Add( laterIndex );
			after.Add( earlierIndex );

			return true;
		}

		// Add initial constraints

		foreach ( var initialConstraint in _initialConstraints )
		{
			if ( !AddWorkingConstraint( initialConstraint.EarlierIndex, initialConstraint.LaterIndex, out invalidConstraint ) )
				return false;
		}

		// Everything in _first should be before everything in _last

		foreach ( var earlierIndex in _first )
		{
			foreach ( var laterIndex in _last )
			{
				if ( !AddWorkingConstraint( earlierIndex, laterIndex, out invalidConstraint ) )
					return false;
			}
		}

		// Keep propagating constraints until nothing changes

		while ( newConstraints.TryDequeue( out var nextConstraint ) )
		{
			// if a < b, and b < c, then a < c etc

			if ( beforeDict.TryGetValue( nextConstraint.LaterIndex, out var before ) )
			{
				foreach ( var laterIndex in before )
				{
					if ( !AddWorkingConstraint( nextConstraint.EarlierIndex, laterIndex, out invalidConstraint ) )
						return false;
				}
			}

			if ( afterDict.TryGetValue( nextConstraint.EarlierIndex, out var after ) )
			{
				foreach ( var earlierIndex in after )
				{
					if ( !AddWorkingConstraint( earlierIndex, nextConstraint.LaterIndex, out invalidConstraint ) )
					{
						return false;
					}
				}
			}
		}

		// Now if we have any items that aren't using GroupOrder.First, and haven't
		// determined that they are ordered before another item with GroupOrder.First,
		// we can safely order them after all GroupOrder.First items. And vice versa.

		foreach ( var middleIndex in middle )
		{
			var isBeforeAnyFirst = beforeDict.TryGetValue( middleIndex, out var before )
				&& before.Any( x => _first.Contains( x ) );

			var isAfterAnyLast = afterDict.TryGetValue( middleIndex, out var after )
				&& after.Any( x => _last.Contains( x ) );

			if ( !isBeforeAnyFirst )
			{
				foreach ( var earlierIndex in _first )
					AddWorkingConstraint( earlierIndex, middleIndex, out invalidConstraint );
			}

			if ( !isAfterAnyLast )
			{
				foreach ( var laterIndex in _last )
					AddWorkingConstraint( middleIndex, laterIndex, out invalidConstraint );
			}
		}

		// Now lets add items to the final ordering if all items that should be sorted
		// before them are already added to that ordering. We'll implement this by choosing
		// items that have an empty list / don't appear in afterDict, and update that
		// dictionary as we go.

		var earliestRemaining = new Queue<int>();

		// First, seed the queue with everything that's already not ordered after anything

		for ( var index = 0; index < _itemCount; ++index )
		{
			if ( !afterDict.ContainsKey( index ) )
			{
				earliestRemaining.Enqueue( index );
			}
		}

		result.Clear();

		while ( earliestRemaining.TryDequeue( out var nextIndex ) )
		{
			result.Add( nextIndex );

			foreach ( var laterIndex in beforeDict.TryGetValue( nextIndex, out var laterIndices )
				? laterIndices : Enumerable.Empty<int>() )
			{
				var beforeLater = afterDict[laterIndex];
				beforeLater.Remove( nextIndex );

				if ( beforeLater.Count == 0 )
					earliestRemaining.Enqueue( laterIndex );
			}
		}

		invalidConstraint = default;
		return result.Count == _itemCount;
	}
}
facepunch.libevents / SortingHelper.cs
Game library
using System.Collections.Generic;
using System.Linq;

namespace Sandbox.Events;

/// <summary>
/// Generate an ordering based on a set of first-most and last-most items, and
/// individual constraints between pairs of items. All first-most items will be
/// ordered before all last-most items, and any other items will be put in the
/// middle unless forced to be elsewhere by a constraint.
/// </summary>
internal class SortingHelper
{
	public record struct SortConstraint( int EarlierIndex, int LaterIndex )
	{
		public SortConstraint Complement => new ( LaterIndex, EarlierIndex );
	}

	private readonly int _itemCount;

	private readonly HashSet<SortConstraint> _initialConstraints = new HashSet<SortConstraint>();

	private readonly HashSet<int> _first = new HashSet<int>();
	private readonly HashSet<int> _last = new HashSet<int>();

	public SortingHelper( int itemCount )
	{
		_itemCount = itemCount;
	}

	public void AddConstraint( int earlierIndex, int laterIndex )
	{
		_initialConstraints.Add( new SortConstraint( earlierIndex, laterIndex ) );
	}

	public void AddFirst( int earlierIndex )
	{
		_first.Add( earlierIndex );
	}

	public void AddLast( int laterIndex )
	{
		_last.Add( laterIndex );
	}

	public bool Sort( List<int> result, out SortConstraint invalidConstraint )
	{
		var middle = new HashSet<int>();

		for ( var index = 0; index < _itemCount; ++index )
		{
			if ( !_first.Contains( index ) && !_last.Contains( index ) )
				middle.Add( index );
		}

		var allConstraints = new HashSet<SortConstraint>();
		var newConstraints = new Queue<SortConstraint>();
		var beforeDict = new Dictionary<int, HashSet<int>>();
		var afterDict = new Dictionary<int, HashSet<int>>();

		bool AddWorkingConstraint( int earlierIndex, int laterIndex, out SortConstraint constraint )
		{
			constraint = new SortConstraint( earlierIndex, laterIndex );

			if ( allConstraints.Contains( constraint.Complement ) )
				return false;

			if ( !allConstraints.Add( constraint ) )
				return true;

			newConstraints.Enqueue( constraint );

			if ( !beforeDict.TryGetValue( earlierIndex, out var before ) )
				beforeDict.Add( earlierIndex, before = new HashSet<int>() );

			if ( !afterDict.TryGetValue( laterIndex, out var after ) )
				afterDict.Add( laterIndex, after = new HashSet<int>() );

			before.Add( laterIndex );
			after.Add( earlierIndex );

			return true;
		}

		// Add initial constraints

		foreach ( var initialConstraint in _initialConstraints )
		{
			if ( !AddWorkingConstraint( initialConstraint.EarlierIndex, initialConstraint.LaterIndex, out invalidConstraint ) )
				return false;
		}

		// Everything in _first should be before everything in _last

		foreach ( var earlierIndex in _first )
		{
			foreach ( var laterIndex in _last )
			{
				if ( !AddWorkingConstraint( earlierIndex, laterIndex, out invalidConstraint ) )
					return false;
			}
		}

		// Keep propagating constraints until nothing changes

		while ( newConstraints.TryDequeue( out var nextConstraint ) )
		{
			// if a < b, and b < c, then a < c etc

			if ( beforeDict.TryGetValue( nextConstraint.LaterIndex, out var before ) )
			{
				foreach ( var laterIndex in before )
				{
					if ( !AddWorkingConstraint( nextConstraint.EarlierIndex, laterIndex, out invalidConstraint ) )
						return false;
				}
			}

			if ( afterDict.TryGetValue( nextConstraint.EarlierIndex, out var after ) )
			{
				foreach ( var earlierIndex in after )
				{
					if ( !AddWorkingConstraint( earlierIndex, nextConstraint.LaterIndex, out invalidConstraint ) )
					{
						return false;
					}
				}
			}
		}

		// Now if we have any items that aren't using GroupOrder.First, and haven't
		// determined that they are ordered before another item with GroupOrder.First,
		// we can safely order them after all GroupOrder.First items. And vice versa.

		foreach ( var middleIndex in middle )
		{
			var isBeforeAnyFirst = beforeDict.TryGetValue( middleIndex, out var before )
				&& before.Any( x => _first.Contains( x ) );

			var isAfterAnyLast = afterDict.TryGetValue( middleIndex, out var after )
				&& after.Any( x => _last.Contains( x ) );

			if ( !isBeforeAnyFirst )
			{
				foreach ( var earlierIndex in _first )
					AddWorkingConstraint( earlierIndex, middleIndex, out invalidConstraint );
			}

			if ( !isAfterAnyLast )
			{
				foreach ( var laterIndex in _last )
					AddWorkingConstraint( middleIndex, laterIndex, out invalidConstraint );
			}
		}

		// Now lets add items to the final ordering if all items that should be sorted
		// before them are already added to that ordering. We'll implement this by choosing
		// items that have an empty list / don't appear in afterDict, and update that
		// dictionary as we go.

		var earliestRemaining = new Queue<int>();

		// First, seed the queue with everything that's already not ordered after anything

		for ( var index = 0; index < _itemCount; ++index )
		{
			if ( !afterDict.ContainsKey( index ) )
			{
				earliestRemaining.Enqueue( index );
			}
		}

		result.Clear();

		while ( earliestRemaining.TryDequeue( out var nextIndex ) )
		{
			result.Add( nextIndex );

			foreach ( var laterIndex in beforeDict.TryGetValue( nextIndex, out var laterIndices )
				? laterIndices : Enumerable.Empty<int>() )
			{
				var beforeLater = afterDict[laterIndex];
				beforeLater.Remove( nextIndex );

				if ( beforeLater.Count == 0 )
					earliestRemaining.Enqueue( laterIndex );
			}
		}

		invalidConstraint = default;
		return result.Count == _itemCount;
	}
}
facepunch.libevents / StateMachine.cs
Game library
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox.Diagnostics;

namespace Sandbox.States;

[Title( "State Machine" ), Icon( "smart_toy" ), Category( "State Machines" )]
public sealed class StateMachineComponent : Component
{
	/// <summary>
	/// How many instant state transitions in a row until we throw an error?
	/// </summary>
	public const int MaxInstantTransitions = 16;

	private readonly Dictionary<int, State> _states = new();
	private readonly Dictionary<int, Transition> _transitions = new();

	private int _nextId = 0;

	/// <summary>
	/// All states in this machine.
	/// </summary>
	public IEnumerable<State> States => _states.Values;

	/// <summary>
	/// All transitions between states in this machine.
	/// </summary>
	public IEnumerable<Transition> Transitions => _transitions.Values;

	/// <summary>
	/// Which state becomes active when the machine starts?
	/// </summary>
	public State? InitialState { get; set; }

	/// <summary>
	/// Which state is currently active?
	/// </summary>
	public State? CurrentState
	{
		get => CurrentStateId is {} id ? _states!.GetValueOrDefault( id ) : null;
		private set => CurrentStateId = value?.Id;
	}

	[Property] private int? CurrentStateId { get; set; }

	private float _stateTime;

	private bool _firstUpdate = true;

	protected override void OnStart()
	{
		if ( !Network.IsProxy && InitialState is { } initial )
		{
			CurrentState = initial;
		}
	}

	private static void InvokeSafe( Action? action )
	{
		try
		{
			action?.Invoke();
		}
		catch ( Exception ex )
		{
			Log.Error( ex );
		}
	}

	protected override void OnFixedUpdate()
	{
		if ( _firstUpdate )
		{
			_firstUpdate = false;

			InvokeSafe( CurrentState?.OnEnterState );
		}

		if ( !Network.IsProxy )
		{
			var transitions = 0;
			var prevTime = _stateTime;

			_stateTime += Time.Delta;

			while ( transitions++ < MaxInstantTransitions && CurrentState?.GetNextTransition( prevTime, _stateTime ) is { } transition )
			{
				DoTransition( transition.Id );

				prevTime = 0f;

				if ( transition.Delay is { } delay )
				{
					_stateTime -= delay;
				}
				else
				{
					_stateTime = 0f;
				}
			}
		}

		InvokeSafe( CurrentState?.OnUpdateState );
	}

	[Broadcast( NetPermission.OwnerOnly )]
	private void DoTransition( int transitionId )
	{
		var transition = _transitions!.GetValueOrDefault( transitionId )
			?? throw new Exception( $"Unknown transition id: {transitionId}" );

		var current = CurrentState!;

		Assert.AreEqual( current, transition.Source );

		InvokeSafe( current.OnLeaveState );
		InvokeSafe( transition.OnTransition );

		CurrentState = current = transition.Target;

		InvokeSafe( current.OnEnterState );
	}

	public State AddState()
	{
		var state = new State( this, _nextId++ );

		_states.Add( state.Id, state );

		state.IsValid = true;

		InitialState ??= state;

		return state;
	}

	internal void RemoveState( State state )
	{
		Assert.AreEqual( this, state.StateMachine );
		Assert.AreEqual( state, _states[state.Id] );

		if ( InitialState == state )
		{
			InitialState = null;
		}

		if ( CurrentState == state )
		{
			CurrentState = null;
		}

		var transitions = Transitions
			.Where( x => x.Source == state || x.Target == state )
			.ToArray();

		foreach ( var transition in transitions )
		{
			transition.Remove();
		}

		_states.Remove( state.Id );

		state.IsValid = false;
	}

	internal Transition AddTransition( State source, State target )
	{
		ArgumentNullException.ThrowIfNull( source, nameof( source ) );
		ArgumentNullException.ThrowIfNull( target, nameof( target ) );

		Assert.AreEqual( this, source.StateMachine );
		Assert.AreEqual( this, target.StateMachine );

		var transition = new Transition( _nextId++, source, target );

		_transitions.Add( transition.Id, transition );

		transition.IsValid = true;

		source.InvalidateTransitions();

		return transition;
	}

	internal void RemoveTransition( Transition transition )
	{
		Assert.AreEqual( this, transition.StateMachine );
		Assert.AreEqual( transition, _transitions[transition.Id] );

		_transitions.Remove( transition.Id );

		transition.IsValid = false;
		transition.Source.InvalidateTransitions();
	}

	internal void Clear()
	{
		_states.Clear();
		_transitions.Clear();

		InitialState = null;

		_nextId = 0;
	}

	[Property]
	private Model Serialized
	{
		get => Serialize();
		set => Deserialize( value );
	}

	internal record Model(
		IReadOnlyList<State.Model> States,
		IReadOnlyList<Transition.Model> Transitions,
		int? InitialStateId );

	internal Model Serialize()
	{
		return new Model(
			States.Select( x => x.Serialize() ).OrderBy( x => x.Id ).ToArray(),
			Transitions.Select( x => x.Serialize() ).OrderBy( x => x.Id ).ToArray(),
			InitialState?.Id );
	}

	internal void Deserialize( Model model )
	{
		Clear();

		foreach ( var stateModel in model.States )
		{
			var state = new State( this, stateModel.Id );

			_states.Add( state.Id, state );
			_nextId = Math.Max( _nextId, state.Id + 1 );

			state.Deserialize( stateModel );
		}

		foreach ( var transitionModel in model.Transitions )
		{
			var transition = new Transition( transitionModel.Id,
				_states[transitionModel.SourceId],
				_states[transitionModel.TargetId] );

			_transitions.Add( transition.Id, transition );
			_nextId = Math.Max( _nextId, transition.Id + 1 );

			transition.Deserialize( transitionModel );
		}

		InitialState = model.InitialStateId is { } id ? _states[id] : null;
	}
}
facepunch.libevents / GameEvents/GameEvent.cs
Game library
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Sandbox.Events;

/// <summary>
/// Interface for event payloads that can be listened for by <see cref="IGameEventHandler{T}"/>s.
/// </summary>
public interface IGameEvent { }

/// <summary>
/// Interface for components that handle game events with a payload of type <see cref="T"/>.
/// </summary>
/// <typeparam name="T">Event payload type.</typeparam>
public interface IGameEventHandler<in T>
	where T : IGameEvent
{
	/// <summary>
	/// Called when an event with payload of type <see cref="T"/> is dispatched on a <see cref="GameObject"/>
	/// that contains this component, including on a descendant.
	/// </summary>
	/// <param name="eventArgs">Event payload.</param>
	void OnGameEvent( T eventArgs );
}

/// <summary>
/// Helper for dispatching game events in a scene.
/// </summary>
public static class GameEvent
{
	private static Dictionary<Type, IReadOnlyDictionary<Type, int>> HandlerOrderingCache { get; } = new();

	/// <summary>
	/// Notifies all <see cref="IGameEventHandler{T}"/> components that are within <paramref name="root"/>,
	/// with a payload of type <typeparamref name="T"/>.
	/// </summary>
	public static void Dispatch<T>( this GameObject root, T eventArgs )
		where T : IGameEvent
	{
		var handlers = (root is Scene scene
			? scene.GetAllComponents<IGameEventHandler<T>>() // I think this is more efficient?
			: root.Components.GetAll<IGameEventHandler<T>>())
			.ToArray();

		if ( !HandlerOrderingCache.TryGetValue( typeof(T), out var ordering ) || handlers.Any( x => !ordering.ContainsKey( x.GetType() ) ) )
		{
			ordering = HandlerOrderingCache[typeof(T)] = GetHandlerOrdering<T>();
		}

		List<Exception>? exceptions = null;

		foreach ( var handler in handlers.OrderBy( x => ordering[x.GetType()] ) )
		{
			try
			{
				handler.OnGameEvent( eventArgs );
			}
			catch ( Exception e )
			{
				exceptions ??= new();
				exceptions.Add( e );
			}
		}

		switch ( exceptions?.Count )
		{
			case 1:
				Log.Error( exceptions[0] );
				break;

			case > 1:
				Log.Error( new AggregateException( exceptions ) );
				break;
		}
	}

	private static bool IsImplementingMethodName( string methodName )
	{
		if ( methodName == nameof(IGameEventHandler<IGameEvent>.OnGameEvent) )
		{
			return true;
		}

		return methodName.StartsWith( "Sandbox.Events.IGameEventHandler<" ) && methodName.EndsWith( ">.OnGameEvent" );
	}

	private static MethodDescription? GetImplementation<T>( TypeDescription type )
	{
		foreach ( var method in type.Methods )
		{
			if ( method.IsStatic ) continue;
			if ( method.Parameters.Length != 1 ) continue;
			if ( method.Parameters[0].ParameterType != typeof( T ) ) continue;

			if ( !IsImplementingMethodName( method.Name ) ) continue;

			return method;
		}

		return null;
	}

	private static IReadOnlyDictionary<Type, int> GetHandlerOrdering<T>()
		where T : IGameEvent
	{
		var types = TypeLibrary.GetTypes<IGameEventHandler<T>>().ToArray();
		var helper = new SortingHelper( types.Length );

		for ( var i = 0; i < types.Length; ++i )
		{
			var type = types[i];
			var method = GetImplementation<T>( type );

			if ( method is null )
			{
				Log.Warning( $"Can't find {nameof( IGameEventHandler<T> )}<{typeof( T ).Name}> implementation in {type.Name}!" );
				continue;
			}

			foreach ( var attrib in method.Attributes )
			{
				switch ( attrib )
				{
					case EarlyAttribute:
						helper.AddFirst( i );
						break;

					case LateAttribute:
						helper.AddLast( i );
						break;

					case IBeforeAttribute before:
						for ( var j = 0; j < types.Length; ++j )
						{
							if ( i == j ) continue;

							var other = types[j];

							if ( before.Type.IsAssignableFrom( other.TargetType ) )
							{
								helper.AddConstraint( i, j );
							}
						}

						break;

					case IAfterAttribute after:
						for ( var j = 0; j < types.Length; ++j )
						{
							if ( i == j ) continue;

							var other = types[j];

							if ( after.Type.IsAssignableFrom( other.TargetType ) )
							{
								helper.AddConstraint( j, i );
							}
						}

						break;
				}
			}
		}

		var ordering = new List<int>();

		if ( !helper.Sort( ordering, out var invalid ) )
		{
			Log.Error( $"Invalid event ordering constraint between {types[invalid.EarlierIndex].Name} and {types[invalid.LaterIndex].Name}!" );
			return ImmutableDictionary<Type, int>.Empty;
		}

		return Enumerable.Range( 0, ordering.Count )
			.ToImmutableDictionary( i => types[ordering[i]].TargetType, i => i );
	}
}

public delegate void GameEventAction<in T>( T eventArgs )
	where T : IGameEvent;

/// <summary>
/// Base class for components that expose game events to Action Graph.
/// </summary>
public abstract class GameEventComponent<T> : Component, IGameEventHandler<T>
	where T : IGameEvent
{
	/// <summary>
	/// Action invoked when the <typeparamref name="T"/> event is dispatched.
	/// </summary>
	[Property]
	public GameEventAction<T>? OnEvent { get; set; }

	void IGameEventHandler<T>.OnGameEvent( T eventArgs )
	{
		OnEvent?.Invoke( eventArgs );
	}
}
facepunch.libevents / __gen_RazorNamespace.cs
Game library
global using Microsoft.AspNetCore.Components; 
global using Microsoft.AspNetCore.Components.Rendering;
facepunch.libevents / GameEvents/Attributes.cs
Game library
using System;

namespace Sandbox.Events;

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked before any handlers not marked as early, except if more specific
/// constraints are given (i.e., <see cref="BeforeAttribute{T}"/>, <see cref="AfterAttribute{T}"/>).
/// </summary>
[AttributeUsage( AttributeTargets.Method )]
public sealed class EarlyAttribute : Attribute
{

}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked after any handlers not marked as late, except if more specific
/// constraints are given (i.e., <see cref="BeforeAttribute{T}"/>, <see cref="AfterAttribute{T}"/>).
/// </summary>
[AttributeUsage( AttributeTargets.Method )]
public sealed class LateAttribute : Attribute
{

}

internal interface IBeforeAttribute
{
	Type Type { get; }
}

internal interface IAfterAttribute
{
	Type Type { get; }
}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked before any handlers in the specified type.
/// </summary>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]
public sealed class BeforeAttribute<T> : Attribute, IBeforeAttribute
{
	Type IBeforeAttribute.Type => typeof(T);
}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked after any handlers in the specified type.
/// </summary>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]
public sealed class AfterAttribute<T> : Attribute, IAfterAttribute
{
	Type IAfterAttribute.Type => typeof( T );
}
facepunch.libevents / State.cs
Game library
using System;
using System.Collections.Generic;
using Sandbox.Diagnostics;

namespace Sandbox.States;

public sealed class State : IValid
{
	private readonly List<Transition> _orderedTransitions = new();
	private bool _transitionsDirty = false;

	public StateMachineComponent StateMachine { get; }

	/// <summary>
	/// Unique ID of this state in its containing <see cref="StateMachineComponent"/>.
	/// </summary>
	public int Id { get; }

	/// <summary>
	/// Helpful name of this state.
	/// </summary>
	public string Name { get; set; } = "Unnamed";

	public bool IsValid { get; internal set; }

	public IReadOnlyList<Transition> Transitions
	{
		get
		{
			if ( _transitionsDirty ) UpdateTransitions();
			return _orderedTransitions;
		}
	}

	/// <summary>
	/// Event dispatched on the owner when this state is entered.
	/// </summary>
	public Action? OnEnterState { get; set; }

	/// <summary>
	/// Event dispatched on the owner while this state is active.
	/// </summary>
	public Action? OnUpdateState { get; set; }

	/// <summary>
	/// Event dispatched on the owner when this state is exited.
	/// </summary>
	public Action? OnLeaveState { get; set; }

	public Vector2 EditorPosition { get; set; }

	internal State( StateMachineComponent stateMachine, int id )
	{
		StateMachine = stateMachine;
		Id = id;
	}

	private void UpdateTransitions()
	{
		_transitionsDirty = false;
		_orderedTransitions.Clear();

		foreach ( var transition in StateMachine.Transitions )
		{
			if ( transition.Source == this )
			{
				_orderedTransitions.Add( transition );
			}
		}

		_orderedTransitions.Sort();
	}

	internal Transition? GetNextTransition( float prevTime, float nextTime )
	{
		foreach ( var transition in Transitions )
		{
			if ( transition.Delay is { } delay )
			{
				if ( delay < prevTime || delay > nextTime )
				{
					continue;
				}
			}

			try
			{
				if ( transition.Condition?.Invoke() is not false )
				{
					return transition;
				}
			}
			catch ( Exception e )
			{
				Log.Error( e );
			}
		}

		return null;
	}

	public Transition AddTransition( State target )
	{
		return StateMachine.AddTransition( this, target );
	}

	public void Remove()
	{
		if ( !IsValid ) return;
		StateMachine.RemoveState( this );
	}

	internal void InvalidateTransitions()
	{
		_transitionsDirty = true;
	}

	internal record Model( int Id, string Name, Action? OnEnterState, Action? OnUpdateState, Action? OnLeaveState, Model.UserDataModel? UserData )
	{
		public record UserDataModel( Vector2 Position );
	}

	internal Model Serialize()
	{
		return new Model( Id, Name, OnEnterState, OnUpdateState, OnLeaveState, new Model.UserDataModel( EditorPosition ) );
	}

	internal void Deserialize( Model model )
	{
		Assert.AreEqual( Id, model.Id );

		Name = model.Name;

		OnEnterState = model.OnEnterState;
		OnUpdateState = model.OnUpdateState;
		OnLeaveState = model.OnLeaveState;

		EditorPosition = model.UserData?.Position ?? Vector2.Zero;
	}
}
facepunch.libevents / Code/GameEvents/Attributes.cs
Game library
using System;

namespace Sandbox.Events;

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked before any handlers not marked as early, except if more specific
/// constraints are given (i.e., <see cref="BeforeAttribute{T}"/>, <see cref="AfterAttribute{T}"/>).
/// </summary>
[AttributeUsage( AttributeTargets.Method )]
public sealed class EarlyAttribute : Attribute
{

}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked after any handlers not marked as late, except if more specific
/// constraints are given (i.e., <see cref="BeforeAttribute{T}"/>, <see cref="AfterAttribute{T}"/>).
/// </summary>
[AttributeUsage( AttributeTargets.Method )]
public sealed class LateAttribute : Attribute
{

}

internal interface IBeforeAttribute
{
	Type Type { get; }
}

internal interface IAfterAttribute
{
	Type Type { get; }
}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked before any handlers in the specified type.
/// </summary>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]
public sealed class BeforeAttribute<T> : Attribute, IBeforeAttribute
{
	Type IBeforeAttribute.Type => typeof(T);
}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked after any handlers in the specified type.
/// </summary>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]
public sealed class AfterAttribute<T> : Attribute, IAfterAttribute
{
	Type IAfterAttribute.Type => typeof( T );
}
facepunch.libevents / UnitTests/UnitTest.cs
UnitTest library
global using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Reflection;
using Sandbox.Internal;

namespace Sandbox.Events.Tests;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Application.InitUnitTest();

		var addAssemblyMethod = typeof(TypeLibrary)
			.GetMethod( "AddAssembly", BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(Assembly), typeof(bool) } )!;

		addAssemblyMethod.Invoke( GlobalGameNamespace.TypeLibrary, new object?[] { Assembly.GetExecutingAssembly(), true } );
	}
}
facepunch.libevents / UnitTests/DispatchTests.cs
UnitTest library
namespace Sandbox.Events.Tests;

[TestClass]
public class DispatchTests
{
	[TestMethod]
	public void Simple()
	{
		var scene = new Scene();

		using var _ = scene.Push();

		var go = new GameObject();

		go.Components.Create<EarlyHandler>();
		go.Components.Create<Handler>();
		go.Components.Create<LateHandler>();

		go.Components.Create<AfterLateHandler>();
		go.Components.Create<BeforeLateHandler>();

		go.Components.Create<AfterEarlyHandler>();
		go.Components.Create<BeforeEarlyHandler>();

		go.Components.Create<BeforeHandler>();
		go.Components.Create<AfterHandler>();

		scene.Dispatch( new ExampleEventArgs() );

		Assert.IsTrue( go.Components.Get<EarlyHandler>().Index < go.Components.Get<Handler>().Index );
		Assert.IsTrue( go.Components.Get<Handler>().Index < go.Components.Get<LateHandler>().Index );

		Assert.IsTrue( go.Components.Get<BeforeEarlyHandler>().Index < go.Components.Get<EarlyHandler>().Index );
		Assert.IsTrue( go.Components.Get<EarlyHandler>().Index < go.Components.Get<AfterEarlyHandler>().Index );

		Assert.IsTrue( go.Components.Get<BeforeHandler>().Index < go.Components.Get<Handler>().Index );
		Assert.IsTrue( go.Components.Get<Handler>().Index < go.Components.Get<AfterHandler>().Index );

		Assert.IsTrue( go.Components.Get<BeforeLateHandler>().Index < go.Components.Get<LateHandler>().Index );
		Assert.IsTrue( go.Components.Get<LateHandler>().Index < go.Components.Get<AfterLateHandler>().Index );
	}
}

public class ExampleEventArgs : IGameEvent
{
	public int HandleCount { get; set; }
}

public abstract class BaseHandler : Component
{
	public int Index { get; set; }

	protected void Handle( ExampleEventArgs eventArgs )
	{
		Index = ++eventArgs.HandleCount;
	}
}

public sealed class Handler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}

public sealed class EarlyHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	[Early]
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}

public sealed class LateHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	[Late]
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}

public sealed class BeforeHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	[Before<Handler>]
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}

public sealed class AfterHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	[After<Handler>]
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}

public sealed class BeforeEarlyHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	[Before<EarlyHandler>]
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}

public sealed class AfterEarlyHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	[After<EarlyHandler>]
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}

public sealed class BeforeLateHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	[Before<LateHandler>]
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}

public sealed class AfterLateHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>
{
	[After<LateHandler>]
	void IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );
}
facepunch.libevents / .obj/__compiler_extra.cs
Game library
global using static Sandbox.Internal.GlobalGameNamespace;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Game Events" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "libevents" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "facepunch" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "facepunch.libevents" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "8/11/2024 10:59:39 AM" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "17" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v7.0", FrameworkDisplayName = ".NET 7.0" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.153.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.153.0")]
facepunch.libevents / Code/GameEvents/GameEvent.cs
Game library
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Sandbox.Events;

/// <summary>
/// Interface for event payloads that can be listened for by <see cref="IGameEventHandler{T}"/>s.
/// </summary>
public interface IGameEvent { }

/// <summary>
/// Interface for components that handle game events with a payload of type <see cref="T"/>.
/// </summary>
/// <typeparam name="T">Event payload type.</typeparam>
public interface IGameEventHandler<in T>
	where T : IGameEvent
{
	/// <summary>
	/// Called when an event with payload of type <see cref="T"/> is dispatched on a <see cref="GameObject"/>
	/// that contains this component, including on a descendant.
	/// </summary>
	/// <param name="eventArgs">Event payload.</param>
	void OnGameEvent( T eventArgs );
}

/// <summary>
/// Helper for dispatching game events in a scene.
/// </summary>
public static class GameEvent
{
	private static Dictionary<Type, IReadOnlyDictionary<Type, int>> HandlerOrderingCache { get; } = new();

	/// <summary>
	/// Notifies all <see cref="IGameEventHandler{T}"/> components that are within <paramref name="root"/>,
	/// with a payload of type <typeparamref name="T"/>.
	/// </summary>
	public static void Dispatch<T>( this GameObject root, T eventArgs )
		where T : IGameEvent
	{
		var handlers = (root is Scene scene
			? scene.GetAllComponents<IGameEventHandler<T>>() // I think this is more efficient?
			: root.Components.GetAll<IGameEventHandler<T>>())
			.ToArray();

		if ( !HandlerOrderingCache.TryGetValue( typeof(T), out var ordering ) || handlers.Any( x => !ordering.ContainsKey( x.GetType() ) ) )
		{
			ordering = HandlerOrderingCache[typeof(T)] = GetHandlerOrdering<T>();
		}

		List<Exception>? exceptions = null;

		foreach ( var handler in handlers.OrderBy( x => ordering[x.GetType()] ) )
		{
			try
			{
				handler.OnGameEvent( eventArgs );
			}
			catch ( Exception e )
			{
				exceptions ??= new();
				exceptions.Add( e );
			}
		}

		switch ( exceptions?.Count )
		{
			case 1:
				Log.Error( exceptions[0] );
				break;

			case > 1:
				Log.Error( new AggregateException( exceptions ) );
				break;
		}
	}

	private static bool IsImplementingMethodName( string methodName )
	{
		if ( methodName == nameof(IGameEventHandler<IGameEvent>.OnGameEvent) )
		{
			return true;
		}

		return methodName.StartsWith( "Sandbox.Events.IGameEventHandler<" ) && methodName.EndsWith( ">.OnGameEvent" );
	}

	private static MethodDescription? GetImplementation<T>( TypeDescription type )
	{
		foreach ( var method in type.Methods )
		{
			if ( method.IsStatic ) continue;
			if ( method.Parameters.Length != 1 ) continue;
			if ( method.Parameters[0].ParameterType != typeof( T ) ) continue;

			if ( !IsImplementingMethodName( method.Name ) ) continue;

			return method;
		}

		return null;
	}

	private static IReadOnlyDictionary<Type, int> GetHandlerOrdering<T>()
		where T : IGameEvent
	{
		var types = TypeLibrary.GetTypes<IGameEventHandler<T>>().ToArray();
		var helper = new SortingHelper( types.Length );

		for ( var i = 0; i < types.Length; ++i )
		{
			var type = types[i];
			var method = GetImplementation<T>( type );

			if ( method is null )
			{
				Log.Warning( $"Can't find {nameof( IGameEventHandler<T> )}<{typeof( T ).Name}> implementation in {type.Name}!" );
				continue;
			}

			foreach ( var attrib in method.Attributes )
			{
				switch ( attrib )
				{
					case EarlyAttribute:
						helper.AddFirst( i );
						break;

					case LateAttribute:
						helper.AddLast( i );
						break;

					case IBeforeAttribute before:
						for ( var j = 0; j < types.Length; ++j )
						{
							if ( i == j ) continue;

							var other = types[j];

							if ( before.Type.IsAssignableFrom( other.TargetType ) )
							{
								helper.AddConstraint( i, j );
							}
						}

						break;

					case IAfterAttribute after:
						for ( var j = 0; j < types.Length; ++j )
						{
							if ( i == j ) continue;

							var other = types[j];

							if ( after.Type.IsAssignableFrom( other.TargetType ) )
							{
								helper.AddConstraint( j, i );
							}
						}

						break;
				}
			}
		}

		var ordering = new List<int>();

		if ( !helper.Sort( ordering, out var invalid ) )
		{
			Log.Error( $"Invalid event ordering constraint between {types[invalid.EarlierIndex].Name} and {types[invalid.LaterIndex].Name}!" );
			return ImmutableDictionary<Type, int>.Empty;
		}

		return Enumerable.Range( 0, ordering.Count )
			.ToImmutableDictionary( i => types[ordering[i]].TargetType, i => i );
	}
}

public delegate void GameEventAction<in T>( T eventArgs )
	where T : IGameEvent;

/// <summary>
/// Base class for components that expose game events to Action Graph.
/// </summary>
public abstract class GameEventComponent<T> : Component, IGameEventHandler<T>
	where T : IGameEvent
{
	/// <summary>
	/// Action invoked when the <typeparamref name="T"/> event is dispatched.
	/// </summary>
	[Property]
	public GameEventAction<T>? OnEvent { get; set; }

	void IGameEventHandler<T>.OnGameEvent( T eventArgs )
	{
		OnEvent?.Invoke( eventArgs );
	}
}
facepunch.libevents / Transition.cs
Game library
using System;
using Sandbox.Diagnostics;

namespace Sandbox.States;

public sealed class Transition : IComparable<Transition>, IValid
{
	private float? _delay;
	private Func<bool>? _condition;

	/// <summary>
	/// The state machine containing this transition.
	/// </summary>
	public StateMachineComponent StateMachine => Source.StateMachine;

	/// <summary>
	/// Unique ID of this transition in the <see cref="StateMachineComponent"/>.
	/// </summary>
	public int Id { get; }

	/// <summary>
	/// The state this transition originates from.
	/// </summary>
	public State Source { get; }

	/// <summary>
	/// The destination of this transition.
	/// </summary>
	public State Target { get; }

	/// <summary>
	/// Does this transition still belong to a state.
	/// </summary>
	public bool IsValid { get; internal set; }

	internal Transition( int id, State source, State target )
	{
		Source = source;
		Target = target;
		Id = id;
	}

	/// <summary>
	/// Optional delay before this transition is taken.
	/// If null, this transition can be taken at any time.
	/// </summary>
	public float? Delay
	{
		get => _delay;
		set
		{
			_delay = value;
			Source.InvalidateTransitions();
		}
	}

	/// <summary>
	/// Optional condition to evaluate.
	/// </summary>
	public Func<bool>? Condition
	{
		get => _condition;
		set
		{
			_condition = value;
			Source.InvalidateTransitions();
		}
	}

	/// <summary>
	/// Action performed when this transition is taken.
	/// </summary>
	public Action? OnTransition { get; set; }

	public void Remove()
	{
		if ( !IsValid ) return;
		StateMachine.RemoveTransition( this );
	}

	public int CompareTo( Transition? other )
	{
		if ( other is null ) return 1;

		var delayCompare = (Delay ?? float.PositiveInfinity).CompareTo( other.Delay ?? float.PositiveInfinity );
		if ( delayCompare != 0 ) return delayCompare;

		var conditionCompare = (Condition is null).CompareTo( other.Condition is null );
		if ( conditionCompare != 0 ) return conditionCompare;

		return Target.Id.CompareTo( other.Target.Id );
	}

	internal record Model( int Id, int SourceId, int TargetId, float? Delay, Func<bool>? Condition, Action? OnTransition );

	internal Model Serialize()
	{
		return new Model( Id, Source.Id, Target.Id, Delay, Condition, OnTransition );
	}

	internal void Deserialize( Model model )
	{
		Assert.AreEqual( Id, model.Id );
		Assert.AreEqual( Source.Id, model.SourceId );
		Assert.AreEqual( Target.Id, model.TargetId );

		Delay = model.Delay;
		Condition = model.Condition;
		OnTransition = model.OnTransition;
	}
}
Debug: View Raw JSON Response
{
    "TotalCount": 13,
    "Files": [
        {
            "Ident": "facepunch.libevents",
            "Path": "Code/SortingHelper.cs",
            "FileName": "SortingHelper.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "using System.Collections.Generic;\r\nusing System.Linq;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Generate an ordering based on a set of first-most and last-most items, and\r\n/// individual constraints between pairs of items. All first-most items will be\r\n/// ordered before all last-most items, and any other items will be put in the\r\n/// middle unless forced to be elsewhere by a constraint.\r\n/// </summary>\r\ninternal class SortingHelper\r\n{\r\n\tpublic record struct SortConstraint( int EarlierIndex, int LaterIndex )\r\n\t{\r\n\t\tpublic SortConstraint Complement => new ( LaterIndex, EarlierIndex );\r\n\t}\r\n\r\n\tprivate readonly int _itemCount;\r\n\r\n\tprivate readonly HashSet<SortConstraint> _initialConstraints = new HashSet<SortConstraint>();\r\n\r\n\tprivate readonly HashSet<int> _first = new HashSet<int>();\r\n\tprivate readonly HashSet<int> _last = new HashSet<int>();\r\n\r\n\tpublic SortingHelper( int itemCount )\r\n\t{\r\n\t\t_itemCount = itemCount;\r\n\t}\r\n\r\n\tpublic void AddConstraint( int earlierIndex, int laterIndex )\r\n\t{\r\n\t\t_initialConstraints.Add( new SortConstraint( earlierIndex, laterIndex ) );\r\n\t}\r\n\r\n\tpublic void AddFirst( int earlierIndex )\r\n\t{\r\n\t\t_first.Add( earlierIndex );\r\n\t}\r\n\r\n\tpublic void AddLast( int laterIndex )\r\n\t{\r\n\t\t_last.Add( laterIndex );\r\n\t}\r\n\r\n\tpublic bool Sort( List<int> result, out SortConstraint invalidConstraint )\r\n\t{\r\n\t\tvar middle = new HashSet<int>();\r\n\r\n\t\tfor ( var index = 0; index < _itemCount; ++index )\r\n\t\t{\r\n\t\t\tif ( !_first.Contains( index ) && !_last.Contains( index ) )\r\n\t\t\t\tmiddle.Add( index );\r\n\t\t}\r\n\r\n\t\tvar allConstraints = new HashSet<SortConstraint>();\r\n\t\tvar newConstraints = new Queue<SortConstraint>();\r\n\t\tvar beforeDict = new Dictionary<int, HashSet<int>>();\r\n\t\tvar afterDict = new Dictionary<int, HashSet<int>>();\r\n\r\n\t\tbool AddWorkingConstraint( int earlierIndex, int laterIndex, out SortConstraint constraint )\r\n\t\t{\r\n\t\t\tconstraint = new SortConstraint( earlierIndex, laterIndex );\r\n\r\n\t\t\tif ( allConstraints.Contains( constraint.Complement ) )\r\n\t\t\t\treturn false;\r\n\r\n\t\t\tif ( !allConstraints.Add( constraint ) )\r\n\t\t\t\treturn true;\r\n\r\n\t\t\tnewConstraints.Enqueue( constraint );\r\n\r\n\t\t\tif ( !beforeDict.TryGetValue( earlierIndex, out var before ) )\r\n\t\t\t\tbeforeDict.Add( earlierIndex, before = new HashSet<int>() );\r\n\r\n\t\t\tif ( !afterDict.TryGetValue( laterIndex, out var after ) )\r\n\t\t\t\tafterDict.Add( laterIndex, after = new HashSet<int>() );\r\n\r\n\t\t\tbefore.Add( laterIndex );\r\n\t\t\tafter.Add( earlierIndex );\r\n\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t// Add initial constraints\r\n\r\n\t\tforeach ( var initialConstraint in _initialConstraints )\r\n\t\t{\r\n\t\t\tif ( !AddWorkingConstraint( initialConstraint.EarlierIndex, initialConstraint.LaterIndex, out invalidConstraint ) )\r\n\t\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Everything in _first should be before everything in _last\r\n\r\n\t\tforeach ( var earlierIndex in _first )\r\n\t\t{\r\n\t\t\tforeach ( var laterIndex in _last )\r\n\t\t\t{\r\n\t\t\t\tif ( !AddWorkingConstraint( earlierIndex, laterIndex, out invalidConstraint ) )\r\n\t\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Keep propagating constraints until nothing changes\r\n\r\n\t\twhile ( newConstraints.TryDequeue( out var nextConstraint ) )\r\n\t\t{\r\n\t\t\t// if a < b, and b < c, then a < c etc\r\n\r\n\t\t\tif ( beforeDict.TryGetValue( nextConstraint.LaterIndex, out var before ) )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var laterIndex in before )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !AddWorkingConstraint( nextConstraint.EarlierIndex, laterIndex, out invalidConstraint ) )\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( afterDict.TryGetValue( nextConstraint.EarlierIndex, out var after ) )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var earlierIndex in after )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !AddWorkingConstraint( earlierIndex, nextConstraint.LaterIndex, out invalidConstraint ) )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Now if we have any items that aren't using GroupOrder.First, and haven't\r\n\t\t// determined that they are ordered before another item with GroupOrder.First,\r\n\t\t// we can safely order them after all GroupOrder.First items. And vice versa.\r\n\r\n\t\tforeach ( var middleIndex in middle )\r\n\t\t{\r\n\t\t\tvar isBeforeAnyFirst = beforeDict.TryGetValue( middleIndex, out var before )\r\n\t\t\t\t&& before.Any( x => _first.Contains( x ) );\r\n\r\n\t\t\tvar isAfterAnyLast = afterDict.TryGetValue( middleIndex, out var after )\r\n\t\t\t\t&& after.Any( x => _last.Contains( x ) );\r\n\r\n\t\t\tif ( !isBeforeAnyFirst )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var earlierIndex in _first )\r\n\t\t\t\t\tAddWorkingConstraint( earlierIndex, middleIndex, out invalidConstraint );\r\n\t\t\t}\r\n\r\n\t\t\tif ( !isAfterAnyLast )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var laterIndex in _last )\r\n\t\t\t\t\tAddWorkingConstraint( middleIndex, laterIndex, out invalidConstraint );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Now lets add items to the final ordering if all items that should be sorted\r\n\t\t// before them are already added to that ordering. We'll implement this by choosing\r\n\t\t// items that have an empty list / don't appear in afterDict, and update that\r\n\t\t// dictionary as we go.\r\n\r\n\t\tvar earliestRemaining = new Queue<int>();\r\n\r\n\t\t// First, seed the queue with everything that's already not ordered after anything\r\n\r\n\t\tfor ( var index = 0; index < _itemCount; ++index )\r\n\t\t{\r\n\t\t\tif ( !afterDict.ContainsKey( index ) )\r\n\t\t\t{\r\n\t\t\t\tearliestRemaining.Enqueue( index );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tresult.Clear();\r\n\r\n\t\twhile ( earliestRemaining.TryDequeue( out var nextIndex ) )\r\n\t\t{\r\n\t\t\tresult.Add( nextIndex );\r\n\r\n\t\t\tforeach ( var laterIndex in beforeDict.TryGetValue( nextIndex, out var laterIndices )\r\n\t\t\t\t? laterIndices : Enumerable.Empty<int>() )\r\n\t\t\t{\r\n\t\t\t\tvar beforeLater = afterDict[laterIndex];\r\n\t\t\t\tbeforeLater.Remove( nextIndex );\r\n\r\n\t\t\t\tif ( beforeLater.Count == 0 )\r\n\t\t\t\t\tearliestRemaining.Enqueue( laterIndex );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tinvalidConstraint = default;\r\n\t\treturn result.Count == _itemCount;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "SortingHelper.cs",
            "FileName": "SortingHelper.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "using System.Collections.Generic;\r\nusing System.Linq;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Generate an ordering based on a set of first-most and last-most items, and\r\n/// individual constraints between pairs of items. All first-most items will be\r\n/// ordered before all last-most items, and any other items will be put in the\r\n/// middle unless forced to be elsewhere by a constraint.\r\n/// </summary>\r\ninternal class SortingHelper\r\n{\r\n\tpublic record struct SortConstraint( int EarlierIndex, int LaterIndex )\r\n\t{\r\n\t\tpublic SortConstraint Complement => new ( LaterIndex, EarlierIndex );\r\n\t}\r\n\r\n\tprivate readonly int _itemCount;\r\n\r\n\tprivate readonly HashSet<SortConstraint> _initialConstraints = new HashSet<SortConstraint>();\r\n\r\n\tprivate readonly HashSet<int> _first = new HashSet<int>();\r\n\tprivate readonly HashSet<int> _last = new HashSet<int>();\r\n\r\n\tpublic SortingHelper( int itemCount )\r\n\t{\r\n\t\t_itemCount = itemCount;\r\n\t}\r\n\r\n\tpublic void AddConstraint( int earlierIndex, int laterIndex )\r\n\t{\r\n\t\t_initialConstraints.Add( new SortConstraint( earlierIndex, laterIndex ) );\r\n\t}\r\n\r\n\tpublic void AddFirst( int earlierIndex )\r\n\t{\r\n\t\t_first.Add( earlierIndex );\r\n\t}\r\n\r\n\tpublic void AddLast( int laterIndex )\r\n\t{\r\n\t\t_last.Add( laterIndex );\r\n\t}\r\n\r\n\tpublic bool Sort( List<int> result, out SortConstraint invalidConstraint )\r\n\t{\r\n\t\tvar middle = new HashSet<int>();\r\n\r\n\t\tfor ( var index = 0; index < _itemCount; ++index )\r\n\t\t{\r\n\t\t\tif ( !_first.Contains( index ) && !_last.Contains( index ) )\r\n\t\t\t\tmiddle.Add( index );\r\n\t\t}\r\n\r\n\t\tvar allConstraints = new HashSet<SortConstraint>();\r\n\t\tvar newConstraints = new Queue<SortConstraint>();\r\n\t\tvar beforeDict = new Dictionary<int, HashSet<int>>();\r\n\t\tvar afterDict = new Dictionary<int, HashSet<int>>();\r\n\r\n\t\tbool AddWorkingConstraint( int earlierIndex, int laterIndex, out SortConstraint constraint )\r\n\t\t{\r\n\t\t\tconstraint = new SortConstraint( earlierIndex, laterIndex );\r\n\r\n\t\t\tif ( allConstraints.Contains( constraint.Complement ) )\r\n\t\t\t\treturn false;\r\n\r\n\t\t\tif ( !allConstraints.Add( constraint ) )\r\n\t\t\t\treturn true;\r\n\r\n\t\t\tnewConstraints.Enqueue( constraint );\r\n\r\n\t\t\tif ( !beforeDict.TryGetValue( earlierIndex, out var before ) )\r\n\t\t\t\tbeforeDict.Add( earlierIndex, before = new HashSet<int>() );\r\n\r\n\t\t\tif ( !afterDict.TryGetValue( laterIndex, out var after ) )\r\n\t\t\t\tafterDict.Add( laterIndex, after = new HashSet<int>() );\r\n\r\n\t\t\tbefore.Add( laterIndex );\r\n\t\t\tafter.Add( earlierIndex );\r\n\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t// Add initial constraints\r\n\r\n\t\tforeach ( var initialConstraint in _initialConstraints )\r\n\t\t{\r\n\t\t\tif ( !AddWorkingConstraint( initialConstraint.EarlierIndex, initialConstraint.LaterIndex, out invalidConstraint ) )\r\n\t\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Everything in _first should be before everything in _last\r\n\r\n\t\tforeach ( var earlierIndex in _first )\r\n\t\t{\r\n\t\t\tforeach ( var laterIndex in _last )\r\n\t\t\t{\r\n\t\t\t\tif ( !AddWorkingConstraint( earlierIndex, laterIndex, out invalidConstraint ) )\r\n\t\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Keep propagating constraints until nothing changes\r\n\r\n\t\twhile ( newConstraints.TryDequeue( out var nextConstraint ) )\r\n\t\t{\r\n\t\t\t// if a < b, and b < c, then a < c etc\r\n\r\n\t\t\tif ( beforeDict.TryGetValue( nextConstraint.LaterIndex, out var before ) )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var laterIndex in before )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !AddWorkingConstraint( nextConstraint.EarlierIndex, laterIndex, out invalidConstraint ) )\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( afterDict.TryGetValue( nextConstraint.EarlierIndex, out var after ) )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var earlierIndex in after )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !AddWorkingConstraint( earlierIndex, nextConstraint.LaterIndex, out invalidConstraint ) )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Now if we have any items that aren't using GroupOrder.First, and haven't\r\n\t\t// determined that they are ordered before another item with GroupOrder.First,\r\n\t\t// we can safely order them after all GroupOrder.First items. And vice versa.\r\n\r\n\t\tforeach ( var middleIndex in middle )\r\n\t\t{\r\n\t\t\tvar isBeforeAnyFirst = beforeDict.TryGetValue( middleIndex, out var before )\r\n\t\t\t\t&& before.Any( x => _first.Contains( x ) );\r\n\r\n\t\t\tvar isAfterAnyLast = afterDict.TryGetValue( middleIndex, out var after )\r\n\t\t\t\t&& after.Any( x => _last.Contains( x ) );\r\n\r\n\t\t\tif ( !isBeforeAnyFirst )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var earlierIndex in _first )\r\n\t\t\t\t\tAddWorkingConstraint( earlierIndex, middleIndex, out invalidConstraint );\r\n\t\t\t}\r\n\r\n\t\t\tif ( !isAfterAnyLast )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var laterIndex in _last )\r\n\t\t\t\t\tAddWorkingConstraint( middleIndex, laterIndex, out invalidConstraint );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Now lets add items to the final ordering if all items that should be sorted\r\n\t\t// before them are already added to that ordering. We'll implement this by choosing\r\n\t\t// items that have an empty list / don't appear in afterDict, and update that\r\n\t\t// dictionary as we go.\r\n\r\n\t\tvar earliestRemaining = new Queue<int>();\r\n\r\n\t\t// First, seed the queue with everything that's already not ordered after anything\r\n\r\n\t\tfor ( var index = 0; index < _itemCount; ++index )\r\n\t\t{\r\n\t\t\tif ( !afterDict.ContainsKey( index ) )\r\n\t\t\t{\r\n\t\t\t\tearliestRemaining.Enqueue( index );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tresult.Clear();\r\n\r\n\t\twhile ( earliestRemaining.TryDequeue( out var nextIndex ) )\r\n\t\t{\r\n\t\t\tresult.Add( nextIndex );\r\n\r\n\t\t\tforeach ( var laterIndex in beforeDict.TryGetValue( nextIndex, out var laterIndices )\r\n\t\t\t\t? laterIndices : Enumerable.Empty<int>() )\r\n\t\t\t{\r\n\t\t\t\tvar beforeLater = afterDict[laterIndex];\r\n\t\t\t\tbeforeLater.Remove( nextIndex );\r\n\r\n\t\t\t\tif ( beforeLater.Count == 0 )\r\n\t\t\t\t\tearliestRemaining.Enqueue( laterIndex );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tinvalidConstraint = default;\r\n\t\treturn result.Count == _itemCount;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "StateMachine.cs",
            "FileName": "StateMachine.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Sandbox.Diagnostics;\r\n\r\nnamespace Sandbox.States;\r\n\r\n[Title( \"State Machine\" ), Icon( \"smart_toy\" ), Category( \"State Machines\" )]\r\npublic sealed class StateMachineComponent : Component\r\n{\r\n\t/// <summary>\r\n\t/// How many instant state transitions in a row until we throw an error?\r\n\t/// </summary>\r\n\tpublic const int MaxInstantTransitions = 16;\r\n\r\n\tprivate readonly Dictionary<int, State> _states = new();\r\n\tprivate readonly Dictionary<int, Transition> _transitions = new();\r\n\r\n\tprivate int _nextId = 0;\r\n\r\n\t/// <summary>\r\n\t/// All states in this machine.\r\n\t/// </summary>\r\n\tpublic IEnumerable<State> States => _states.Values;\r\n\r\n\t/// <summary>\r\n\t/// All transitions between states in this machine.\r\n\t/// </summary>\r\n\tpublic IEnumerable<Transition> Transitions => _transitions.Values;\r\n\r\n\t/// <summary>\r\n\t/// Which state becomes active when the machine starts?\r\n\t/// </summary>\r\n\tpublic State? InitialState { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// Which state is currently active?\r\n\t/// </summary>\r\n\tpublic State? CurrentState\r\n\t{\r\n\t\tget => CurrentStateId is {} id ? _states!.GetValueOrDefault( id ) : null;\r\n\t\tprivate set => CurrentStateId = value?.Id;\r\n\t}\r\n\r\n\t[Property] private int? CurrentStateId { get; set; }\r\n\r\n\tprivate float _stateTime;\r\n\r\n\tprivate bool _firstUpdate = true;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tif ( !Network.IsProxy && InitialState is { } initial )\r\n\t\t{\r\n\t\t\tCurrentState = initial;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static void InvokeSafe( Action? action )\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\taction?.Invoke();\r\n\t\t}\r\n\t\tcatch ( Exception ex )\r\n\t\t{\r\n\t\t\tLog.Error( ex );\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\tif ( _firstUpdate )\r\n\t\t{\r\n\t\t\t_firstUpdate = false;\r\n\r\n\t\t\tInvokeSafe( CurrentState?.OnEnterState );\r\n\t\t}\r\n\r\n\t\tif ( !Network.IsProxy )\r\n\t\t{\r\n\t\t\tvar transitions = 0;\r\n\t\t\tvar prevTime = _stateTime;\r\n\r\n\t\t\t_stateTime += Time.Delta;\r\n\r\n\t\t\twhile ( transitions++ < MaxInstantTransitions && CurrentState?.GetNextTransition( prevTime, _stateTime ) is { } transition )\r\n\t\t\t{\r\n\t\t\t\tDoTransition( transition.Id );\r\n\r\n\t\t\t\tprevTime = 0f;\r\n\r\n\t\t\t\tif ( transition.Delay is { } delay )\r\n\t\t\t\t{\r\n\t\t\t\t\t_stateTime -= delay;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t_stateTime = 0f;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tInvokeSafe( CurrentState?.OnUpdateState );\r\n\t}\r\n\r\n\t[Broadcast( NetPermission.OwnerOnly )]\r\n\tprivate void DoTransition( int transitionId )\r\n\t{\r\n\t\tvar transition = _transitions!.GetValueOrDefault( transitionId )\r\n\t\t\t?? throw new Exception( $\"Unknown transition id: {transitionId}\" );\r\n\r\n\t\tvar current = CurrentState!;\r\n\r\n\t\tAssert.AreEqual( current, transition.Source );\r\n\r\n\t\tInvokeSafe( current.OnLeaveState );\r\n\t\tInvokeSafe( transition.OnTransition );\r\n\r\n\t\tCurrentState = current = transition.Target;\r\n\r\n\t\tInvokeSafe( current.OnEnterState );\r\n\t}\r\n\r\n\tpublic State AddState()\r\n\t{\r\n\t\tvar state = new State( this, _nextId++ );\r\n\r\n\t\t_states.Add( state.Id, state );\r\n\r\n\t\tstate.IsValid = true;\r\n\r\n\t\tInitialState ??= state;\r\n\r\n\t\treturn state;\r\n\t}\r\n\r\n\tinternal void RemoveState( State state )\r\n\t{\r\n\t\tAssert.AreEqual( this, state.StateMachine );\r\n\t\tAssert.AreEqual( state, _states[state.Id] );\r\n\r\n\t\tif ( InitialState == state )\r\n\t\t{\r\n\t\t\tInitialState = null;\r\n\t\t}\r\n\r\n\t\tif ( CurrentState == state )\r\n\t\t{\r\n\t\t\tCurrentState = null;\r\n\t\t}\r\n\r\n\t\tvar transitions = Transitions\r\n\t\t\t.Where( x => x.Source == state || x.Target == state )\r\n\t\t\t.ToArray();\r\n\r\n\t\tforeach ( var transition in transitions )\r\n\t\t{\r\n\t\t\ttransition.Remove();\r\n\t\t}\r\n\r\n\t\t_states.Remove( state.Id );\r\n\r\n\t\tstate.IsValid = false;\r\n\t}\r\n\r\n\tinternal Transition AddTransition( State source, State target )\r\n\t{\r\n\t\tArgumentNullException.ThrowIfNull( source, nameof( source ) );\r\n\t\tArgumentNullException.ThrowIfNull( target, nameof( target ) );\r\n\r\n\t\tAssert.AreEqual( this, source.StateMachine );\r\n\t\tAssert.AreEqual( this, target.StateMachine );\r\n\r\n\t\tvar transition = new Transition( _nextId++, source, target );\r\n\r\n\t\t_transitions.Add( transition.Id, transition );\r\n\r\n\t\ttransition.IsValid = true;\r\n\r\n\t\tsource.InvalidateTransitions();\r\n\r\n\t\treturn transition;\r\n\t}\r\n\r\n\tinternal void RemoveTransition( Transition transition )\r\n\t{\r\n\t\tAssert.AreEqual( this, transition.StateMachine );\r\n\t\tAssert.AreEqual( transition, _transitions[transition.Id] );\r\n\r\n\t\t_transitions.Remove( transition.Id );\r\n\r\n\t\ttransition.IsValid = false;\r\n\t\ttransition.Source.InvalidateTransitions();\r\n\t}\r\n\r\n\tinternal void Clear()\r\n\t{\r\n\t\t_states.Clear();\r\n\t\t_transitions.Clear();\r\n\r\n\t\tInitialState = null;\r\n\r\n\t\t_nextId = 0;\r\n\t}\r\n\r\n\t[Property]\r\n\tprivate Model Serialized\r\n\t{\r\n\t\tget => Serialize();\r\n\t\tset => Deserialize( value );\r\n\t}\r\n\r\n\tinternal record Model(\r\n\t\tIReadOnlyList<State.Model> States,\r\n\t\tIReadOnlyList<Transition.Model> Transitions,\r\n\t\tint? InitialStateId );\r\n\r\n\tinternal Model Serialize()\r\n\t{\r\n\t\treturn new Model(\r\n\t\t\tStates.Select( x => x.Serialize() ).OrderBy( x => x.Id ).ToArray(),\r\n\t\t\tTransitions.Select( x => x.Serialize() ).OrderBy( x => x.Id ).ToArray(),\r\n\t\t\tInitialState?.Id );\r\n\t}\r\n\r\n\tinternal void Deserialize( Model model )\r\n\t{\r\n\t\tClear();\r\n\r\n\t\tforeach ( var stateModel in model.States )\r\n\t\t{\r\n\t\t\tvar state = new State( this, stateModel.Id );\r\n\r\n\t\t\t_states.Add( state.Id, state );\r\n\t\t\t_nextId = Math.Max( _nextId, state.Id + 1 );\r\n\r\n\t\t\tstate.Deserialize( stateModel );\r\n\t\t}\r\n\r\n\t\tforeach ( var transitionModel in model.Transitions )\r\n\t\t{\r\n\t\t\tvar transition = new Transition( transitionModel.Id,\r\n\t\t\t\t_states[transitionModel.SourceId],\r\n\t\t\t\t_states[transitionModel.TargetId] );\r\n\r\n\t\t\t_transitions.Add( transition.Id, transition );\r\n\t\t\t_nextId = Math.Max( _nextId, transition.Id + 1 );\r\n\r\n\t\t\ttransition.Deserialize( transitionModel );\r\n\t\t}\r\n\r\n\t\tInitialState = model.InitialStateId is { } id ? _states[id] : null;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "GameEvents/GameEvent.cs",
            "FileName": "GameEvent.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Collections.Immutable;\r\nusing System.Linq;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Interface for event payloads that can be listened for by <see cref=\"IGameEventHandler{T}\"/>s.\r\n/// </summary>\r\npublic interface IGameEvent { }\r\n\r\n/// <summary>\r\n/// Interface for components that handle game events with a payload of type <see cref=\"T\"/>.\r\n/// </summary>\r\n/// <typeparam name=\"T\">Event payload type.</typeparam>\r\npublic interface IGameEventHandler<in T>\r\n\twhere T : IGameEvent\r\n{\r\n\t/// <summary>\r\n\t/// Called when an event with payload of type <see cref=\"T\"/> is dispatched on a <see cref=\"GameObject\"/>\r\n\t/// that contains this component, including on a descendant.\r\n\t/// </summary>\r\n\t/// <param name=\"eventArgs\">Event payload.</param>\r\n\tvoid OnGameEvent( T eventArgs );\r\n}\r\n\r\n/// <summary>\r\n/// Helper for dispatching game events in a scene.\r\n/// </summary>\r\npublic static class GameEvent\r\n{\r\n\tprivate static Dictionary<Type, IReadOnlyDictionary<Type, int>> HandlerOrderingCache { get; } = new();\r\n\r\n\t/// <summary>\r\n\t/// Notifies all <see cref=\"IGameEventHandler{T}\"/> components that are within <paramref name=\"root\"/>,\r\n\t/// with a payload of type <typeparamref name=\"T\"/>.\r\n\t/// </summary>\r\n\tpublic static void Dispatch<T>( this GameObject root, T eventArgs )\r\n\t\twhere T : IGameEvent\r\n\t{\r\n\t\tvar handlers = (root is Scene scene\r\n\t\t\t? scene.GetAllComponents<IGameEventHandler<T>>() // I think this is more efficient?\r\n\t\t\t: root.Components.GetAll<IGameEventHandler<T>>())\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( !HandlerOrderingCache.TryGetValue( typeof(T), out var ordering ) || handlers.Any( x => !ordering.ContainsKey( x.GetType() ) ) )\r\n\t\t{\r\n\t\t\tordering = HandlerOrderingCache[typeof(T)] = GetHandlerOrdering<T>();\r\n\t\t}\r\n\r\n\t\tList<Exception>? exceptions = null;\r\n\r\n\t\tforeach ( var handler in handlers.OrderBy( x => ordering[x.GetType()] ) )\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\thandler.OnGameEvent( eventArgs );\r\n\t\t\t}\r\n\t\t\tcatch ( Exception e )\r\n\t\t\t{\r\n\t\t\t\texceptions ??= new();\r\n\t\t\t\texceptions.Add( e );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tswitch ( exceptions?.Count )\r\n\t\t{\r\n\t\t\tcase 1:\r\n\t\t\t\tLog.Error( exceptions[0] );\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase > 1:\r\n\t\t\t\tLog.Error( new AggregateException( exceptions ) );\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static bool IsImplementingMethodName( string methodName )\r\n\t{\r\n\t\tif ( methodName == nameof(IGameEventHandler<IGameEvent>.OnGameEvent) )\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\treturn methodName.StartsWith( \"Sandbox.Events.IGameEventHandler<\" ) && methodName.EndsWith( \">.OnGameEvent\" );\r\n\t}\r\n\r\n\tprivate static MethodDescription? GetImplementation<T>( TypeDescription type )\r\n\t{\r\n\t\tforeach ( var method in type.Methods )\r\n\t\t{\r\n\t\t\tif ( method.IsStatic ) continue;\r\n\t\t\tif ( method.Parameters.Length != 1 ) continue;\r\n\t\t\tif ( method.Parameters[0].ParameterType != typeof( T ) ) continue;\r\n\r\n\t\t\tif ( !IsImplementingMethodName( method.Name ) ) continue;\r\n\r\n\t\t\treturn method;\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\tprivate static IReadOnlyDictionary<Type, int> GetHandlerOrdering<T>()\r\n\t\twhere T : IGameEvent\r\n\t{\r\n\t\tvar types = TypeLibrary.GetTypes<IGameEventHandler<T>>().ToArray();\r\n\t\tvar helper = new SortingHelper( types.Length );\r\n\r\n\t\tfor ( var i = 0; i < types.Length; ++i )\r\n\t\t{\r\n\t\t\tvar type = types[i];\r\n\t\t\tvar method = GetImplementation<T>( type );\r\n\r\n\t\t\tif ( method is null )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\"Can't find {nameof( IGameEventHandler<T> )}<{typeof( T ).Name}> implementation in {type.Name}!\" );\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tforeach ( var attrib in method.Attributes )\r\n\t\t\t{\r\n\t\t\t\tswitch ( attrib )\r\n\t\t\t\t{\r\n\t\t\t\t\tcase EarlyAttribute:\r\n\t\t\t\t\t\thelper.AddFirst( i );\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase LateAttribute:\r\n\t\t\t\t\t\thelper.AddLast( i );\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase IBeforeAttribute before:\r\n\t\t\t\t\t\tfor ( var j = 0; j < types.Length; ++j )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif ( i == j ) continue;\r\n\r\n\t\t\t\t\t\t\tvar other = types[j];\r\n\r\n\t\t\t\t\t\t\tif ( before.Type.IsAssignableFrom( other.TargetType ) )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\thelper.AddConstraint( i, j );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase IAfterAttribute after:\r\n\t\t\t\t\t\tfor ( var j = 0; j < types.Length; ++j )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif ( i == j ) continue;\r\n\r\n\t\t\t\t\t\t\tvar other = types[j];\r\n\r\n\t\t\t\t\t\t\tif ( after.Type.IsAssignableFrom( other.TargetType ) )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\thelper.AddConstraint( j, i );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar ordering = new List<int>();\r\n\r\n\t\tif ( !helper.Sort( ordering, out var invalid ) )\r\n\t\t{\r\n\t\t\tLog.Error( $\"Invalid event ordering constraint between {types[invalid.EarlierIndex].Name} and {types[invalid.LaterIndex].Name}!\" );\r\n\t\t\treturn ImmutableDictionary<Type, int>.Empty;\r\n\t\t}\r\n\r\n\t\treturn Enumerable.Range( 0, ordering.Count )\r\n\t\t\t.ToImmutableDictionary( i => types[ordering[i]].TargetType, i => i );\r\n\t}\r\n}\r\n\r\npublic delegate void GameEventAction<in T>( T eventArgs )\r\n\twhere T : IGameEvent;\r\n\r\n/// <summary>\r\n/// Base class for components that expose game events to Action Graph.\r\n/// </summary>\r\npublic abstract class GameEventComponent<T> : Component, IGameEventHandler<T>\r\n\twhere T : IGameEvent\r\n{\r\n\t/// <summary>\r\n\t/// Action invoked when the <typeparamref name=\"T\"/> event is dispatched.\r\n\t/// </summary>\r\n\t[Property]\r\n\tpublic GameEventAction<T>? OnEvent { get; set; }\r\n\r\n\tvoid IGameEventHandler<T>.OnGameEvent( T eventArgs )\r\n\t{\r\n\t\tOnEvent?.Invoke( eventArgs );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "__gen_RazorNamespace.cs",
            "FileName": "__gen_RazorNamespace.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "global using Microsoft.AspNetCore.Components; \nglobal using Microsoft.AspNetCore.Components.Rendering;\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "GameEvents/Attributes.cs",
            "FileName": "Attributes.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "\r\nusing System;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked before any handlers not marked as early, except if more specific\r\n/// constraints are given (i.e., <see cref=\"BeforeAttribute{T}\"/>, <see cref=\"AfterAttribute{T}\"/>).\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method )]\r\npublic sealed class EarlyAttribute : Attribute\r\n{\r\n\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked after any handlers not marked as late, except if more specific\r\n/// constraints are given (i.e., <see cref=\"BeforeAttribute{T}\"/>, <see cref=\"AfterAttribute{T}\"/>).\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method )]\r\npublic sealed class LateAttribute : Attribute\r\n{\r\n\r\n}\r\n\r\ninternal interface IBeforeAttribute\r\n{\r\n\tType Type { get; }\r\n}\r\n\r\ninternal interface IAfterAttribute\r\n{\r\n\tType Type { get; }\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked before any handlers in the specified type.\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]\r\npublic sealed class BeforeAttribute<T> : Attribute, IBeforeAttribute\r\n{\r\n\tType IBeforeAttribute.Type => typeof(T);\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked after any handlers in the specified type.\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]\r\npublic sealed class AfterAttribute<T> : Attribute, IAfterAttribute\r\n{\r\n\tType IAfterAttribute.Type => typeof( T );\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "State.cs",
            "FileName": "State.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing Sandbox.Diagnostics;\r\n\r\nnamespace Sandbox.States;\r\n\r\npublic sealed class State : IValid\r\n{\r\n\tprivate readonly List<Transition> _orderedTransitions = new();\r\n\tprivate bool _transitionsDirty = false;\r\n\r\n\tpublic StateMachineComponent StateMachine { get; }\r\n\r\n\t/// <summary>\r\n\t/// Unique ID of this state in its containing <see cref=\"StateMachineComponent\"/>.\r\n\t/// </summary>\r\n\tpublic int Id { get; }\r\n\r\n\t/// <summary>\r\n\t/// Helpful name of this state.\r\n\t/// </summary>\r\n\tpublic string Name { get; set; } = \"Unnamed\";\r\n\r\n\tpublic bool IsValid { get; internal set; }\r\n\r\n\tpublic IReadOnlyList<Transition> Transitions\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tif ( _transitionsDirty ) UpdateTransitions();\r\n\t\t\treturn _orderedTransitions;\r\n\t\t}\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Event dispatched on the owner when this state is entered.\r\n\t/// </summary>\r\n\tpublic Action? OnEnterState { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// Event dispatched on the owner while this state is active.\r\n\t/// </summary>\r\n\tpublic Action? OnUpdateState { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// Event dispatched on the owner when this state is exited.\r\n\t/// </summary>\r\n\tpublic Action? OnLeaveState { get; set; }\r\n\r\n\tpublic Vector2 EditorPosition { get; set; }\r\n\r\n\tinternal State( StateMachineComponent stateMachine, int id )\r\n\t{\r\n\t\tStateMachine = stateMachine;\r\n\t\tId = id;\r\n\t}\r\n\r\n\tprivate void UpdateTransitions()\r\n\t{\r\n\t\t_transitionsDirty = false;\r\n\t\t_orderedTransitions.Clear();\r\n\r\n\t\tforeach ( var transition in StateMachine.Transitions )\r\n\t\t{\r\n\t\t\tif ( transition.Source == this )\r\n\t\t\t{\r\n\t\t\t\t_orderedTransitions.Add( transition );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_orderedTransitions.Sort();\r\n\t}\r\n\r\n\tinternal Transition? GetNextTransition( float prevTime, float nextTime )\r\n\t{\r\n\t\tforeach ( var transition in Transitions )\r\n\t\t{\r\n\t\t\tif ( transition.Delay is { } delay )\r\n\t\t\t{\r\n\t\t\t\tif ( delay < prevTime || delay > nextTime )\r\n\t\t\t\t{\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tif ( transition.Condition?.Invoke() is not false )\r\n\t\t\t\t{\r\n\t\t\t\t\treturn transition;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tcatch ( Exception e )\r\n\t\t\t{\r\n\t\t\t\tLog.Error( e );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\tpublic Transition AddTransition( State target )\r\n\t{\r\n\t\treturn StateMachine.AddTransition( this, target );\r\n\t}\r\n\r\n\tpublic void Remove()\r\n\t{\r\n\t\tif ( !IsValid ) return;\r\n\t\tStateMachine.RemoveState( this );\r\n\t}\r\n\r\n\tinternal void InvalidateTransitions()\r\n\t{\r\n\t\t_transitionsDirty = true;\r\n\t}\r\n\r\n\tinternal record Model( int Id, string Name, Action? OnEnterState, Action? OnUpdateState, Action? OnLeaveState, Model.UserDataModel? UserData )\r\n\t{\r\n\t\tpublic record UserDataModel( Vector2 Position );\r\n\t}\r\n\r\n\tinternal Model Serialize()\r\n\t{\r\n\t\treturn new Model( Id, Name, OnEnterState, OnUpdateState, OnLeaveState, new Model.UserDataModel( EditorPosition ) );\r\n\t}\r\n\r\n\tinternal void Deserialize( Model model )\r\n\t{\r\n\t\tAssert.AreEqual( Id, model.Id );\r\n\r\n\t\tName = model.Name;\r\n\r\n\t\tOnEnterState = model.OnEnterState;\r\n\t\tOnUpdateState = model.OnUpdateState;\r\n\t\tOnLeaveState = model.OnLeaveState;\r\n\r\n\t\tEditorPosition = model.UserData?.Position ?? Vector2.Zero;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "Code/GameEvents/Attributes.cs",
            "FileName": "Attributes.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "\r\nusing System;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked before any handlers not marked as early, except if more specific\r\n/// constraints are given (i.e., <see cref=\"BeforeAttribute{T}\"/>, <see cref=\"AfterAttribute{T}\"/>).\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method )]\r\npublic sealed class EarlyAttribute : Attribute\r\n{\r\n\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked after any handlers not marked as late, except if more specific\r\n/// constraints are given (i.e., <see cref=\"BeforeAttribute{T}\"/>, <see cref=\"AfterAttribute{T}\"/>).\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method )]\r\npublic sealed class LateAttribute : Attribute\r\n{\r\n\r\n}\r\n\r\ninternal interface IBeforeAttribute\r\n{\r\n\tType Type { get; }\r\n}\r\n\r\ninternal interface IAfterAttribute\r\n{\r\n\tType Type { get; }\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked before any handlers in the specified type.\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]\r\npublic sealed class BeforeAttribute<T> : Attribute, IBeforeAttribute\r\n{\r\n\tType IBeforeAttribute.Type => typeof(T);\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked after any handlers in the specified type.\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]\r\npublic sealed class AfterAttribute<T> : Attribute, IAfterAttribute\r\n{\r\n\tType IAfterAttribute.Type => typeof( T );\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "UnitTests/UnitTest.cs",
            "FileName": "UnitTest.cs",
            "PackageType": "library",
            "CodeKind": "UnitTest",
            "AssetVersionId": 65480,
            "Code": "global using Microsoft.VisualStudio.TestTools.UnitTesting;\r\nusing System.Reflection;\r\nusing Sandbox.Internal;\r\n\r\nnamespace Sandbox.Events.Tests;\r\n\r\n[TestClass]\r\npublic class TestInit\r\n{\r\n\t[AssemblyInitialize]\r\n\tpublic static void ClassInitialize( TestContext context )\r\n\t{\r\n\t\tApplication.InitUnitTest();\r\n\r\n\t\tvar addAssemblyMethod = typeof(TypeLibrary)\r\n\t\t\t.GetMethod( \"AddAssembly\", BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(Assembly), typeof(bool) } )!;\r\n\r\n\t\taddAssemblyMethod.Invoke( GlobalGameNamespace.TypeLibrary, new object?[] { Assembly.GetExecutingAssembly(), true } );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "UnitTests/DispatchTests.cs",
            "FileName": "DispatchTests.cs",
            "PackageType": "library",
            "CodeKind": "UnitTest",
            "AssetVersionId": 65480,
            "Code": "namespace Sandbox.Events.Tests;\r\n\r\n[TestClass]\r\npublic class DispatchTests\r\n{\r\n\t[TestMethod]\r\n\tpublic void Simple()\r\n\t{\r\n\t\tvar scene = new Scene();\r\n\r\n\t\tusing var _ = scene.Push();\r\n\r\n\t\tvar go = new GameObject();\r\n\r\n\t\tgo.Components.Create<EarlyHandler>();\r\n\t\tgo.Components.Create<Handler>();\r\n\t\tgo.Components.Create<LateHandler>();\r\n\r\n\t\tgo.Components.Create<AfterLateHandler>();\r\n\t\tgo.Components.Create<BeforeLateHandler>();\r\n\r\n\t\tgo.Components.Create<AfterEarlyHandler>();\r\n\t\tgo.Components.Create<BeforeEarlyHandler>();\r\n\r\n\t\tgo.Components.Create<BeforeHandler>();\r\n\t\tgo.Components.Create<AfterHandler>();\r\n\r\n\t\tscene.Dispatch( new ExampleEventArgs() );\r\n\r\n\t\tAssert.IsTrue( go.Components.Get<EarlyHandler>().Index < go.Components.Get<Handler>().Index );\r\n\t\tAssert.IsTrue( go.Components.Get<Handler>().Index < go.Components.Get<LateHandler>().Index );\r\n\r\n\t\tAssert.IsTrue( go.Components.Get<BeforeEarlyHandler>().Index < go.Components.Get<EarlyHandler>().Index );\r\n\t\tAssert.IsTrue( go.Components.Get<EarlyHandler>().Index < go.Components.Get<AfterEarlyHandler>().Index );\r\n\r\n\t\tAssert.IsTrue( go.Components.Get<BeforeHandler>().Index < go.Components.Get<Handler>().Index );\r\n\t\tAssert.IsTrue( go.Components.Get<Handler>().Index < go.Components.Get<AfterHandler>().Index );\r\n\r\n\t\tAssert.IsTrue( go.Components.Get<BeforeLateHandler>().Index < go.Components.Get<LateHandler>().Index );\r\n\t\tAssert.IsTrue( go.Components.Get<LateHandler>().Index < go.Components.Get<AfterLateHandler>().Index );\r\n\t}\r\n}\r\n\r\npublic class ExampleEventArgs : IGameEvent\r\n{\r\n\tpublic int HandleCount { get; set; }\r\n}\r\n\r\npublic abstract class BaseHandler : Component\r\n{\r\n\tpublic int Index { get; set; }\r\n\r\n\tprotected void Handle( ExampleEventArgs eventArgs )\r\n\t{\r\n\t\tIndex = ++eventArgs.HandleCount;\r\n\t}\r\n}\r\n\r\npublic sealed class Handler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n\r\npublic sealed class EarlyHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\t[Early]\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n\r\npublic sealed class LateHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\t[Late]\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n\r\npublic sealed class BeforeHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\t[Before<Handler>]\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n\r\npublic sealed class AfterHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\t[After<Handler>]\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n\r\npublic sealed class BeforeEarlyHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\t[Before<EarlyHandler>]\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n\r\npublic sealed class AfterEarlyHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\t[After<EarlyHandler>]\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n\r\npublic sealed class BeforeLateHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\t[Before<LateHandler>]\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n\r\npublic sealed class AfterLateHandler : BaseHandler, IGameEventHandler<ExampleEventArgs>\r\n{\r\n\t[After<LateHandler>]\r\n\tvoid IGameEventHandler<ExampleEventArgs>.OnGameEvent( ExampleEventArgs eventArgs ) => Handle( eventArgs );\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": ".obj/__compiler_extra.cs",
            "FileName": "__compiler_extra.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "global using static Sandbox.Internal.GlobalGameNamespace;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", \"Game Events\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"libevents\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"facepunch\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"facepunch.libevents\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", \"8/11/2024 10:59:39 AM\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"17\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", \"1\" )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \".NETCoreApp,Version=v7.0\", FrameworkDisplayName = \".NET 7.0\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.153.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.153.0\")]"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "Code/GameEvents/GameEvent.cs",
            "FileName": "GameEvent.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Collections.Immutable;\r\nusing System.Linq;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Interface for event payloads that can be listened for by <see cref=\"IGameEventHandler{T}\"/>s.\r\n/// </summary>\r\npublic interface IGameEvent { }\r\n\r\n/// <summary>\r\n/// Interface for components that handle game events with a payload of type <see cref=\"T\"/>.\r\n/// </summary>\r\n/// <typeparam name=\"T\">Event payload type.</typeparam>\r\npublic interface IGameEventHandler<in T>\r\n\twhere T : IGameEvent\r\n{\r\n\t/// <summary>\r\n\t/// Called when an event with payload of type <see cref=\"T\"/> is dispatched on a <see cref=\"GameObject\"/>\r\n\t/// that contains this component, including on a descendant.\r\n\t/// </summary>\r\n\t/// <param name=\"eventArgs\">Event payload.</param>\r\n\tvoid OnGameEvent( T eventArgs );\r\n}\r\n\r\n/// <summary>\r\n/// Helper for dispatching game events in a scene.\r\n/// </summary>\r\npublic static class GameEvent\r\n{\r\n\tprivate static Dictionary<Type, IReadOnlyDictionary<Type, int>> HandlerOrderingCache { get; } = new();\r\n\r\n\t/// <summary>\r\n\t/// Notifies all <see cref=\"IGameEventHandler{T}\"/> components that are within <paramref name=\"root\"/>,\r\n\t/// with a payload of type <typeparamref name=\"T\"/>.\r\n\t/// </summary>\r\n\tpublic static void Dispatch<T>( this GameObject root, T eventArgs )\r\n\t\twhere T : IGameEvent\r\n\t{\r\n\t\tvar handlers = (root is Scene scene\r\n\t\t\t? scene.GetAllComponents<IGameEventHandler<T>>() // I think this is more efficient?\r\n\t\t\t: root.Components.GetAll<IGameEventHandler<T>>())\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( !HandlerOrderingCache.TryGetValue( typeof(T), out var ordering ) || handlers.Any( x => !ordering.ContainsKey( x.GetType() ) ) )\r\n\t\t{\r\n\t\t\tordering = HandlerOrderingCache[typeof(T)] = GetHandlerOrdering<T>();\r\n\t\t}\r\n\r\n\t\tList<Exception>? exceptions = null;\r\n\r\n\t\tforeach ( var handler in handlers.OrderBy( x => ordering[x.GetType()] ) )\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\thandler.OnGameEvent( eventArgs );\r\n\t\t\t}\r\n\t\t\tcatch ( Exception e )\r\n\t\t\t{\r\n\t\t\t\texceptions ??= new();\r\n\t\t\t\texceptions.Add( e );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tswitch ( exceptions?.Count )\r\n\t\t{\r\n\t\t\tcase 1:\r\n\t\t\t\tLog.Error( exceptions[0] );\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase > 1:\r\n\t\t\t\tLog.Error( new AggregateException( exceptions ) );\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static bool IsImplementingMethodName( string methodName )\r\n\t{\r\n\t\tif ( methodName == nameof(IGameEventHandler<IGameEvent>.OnGameEvent) )\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\treturn methodName.StartsWith( \"Sandbox.Events.IGameEventHandler<\" ) && methodName.EndsWith( \">.OnGameEvent\" );\r\n\t}\r\n\r\n\tprivate static MethodDescription? GetImplementation<T>( TypeDescription type )\r\n\t{\r\n\t\tforeach ( var method in type.Methods )\r\n\t\t{\r\n\t\t\tif ( method.IsStatic ) continue;\r\n\t\t\tif ( method.Parameters.Length != 1 ) continue;\r\n\t\t\tif ( method.Parameters[0].ParameterType != typeof( T ) ) continue;\r\n\r\n\t\t\tif ( !IsImplementingMethodName( method.Name ) ) continue;\r\n\r\n\t\t\treturn method;\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\tprivate static IReadOnlyDictionary<Type, int> GetHandlerOrdering<T>()\r\n\t\twhere T : IGameEvent\r\n\t{\r\n\t\tvar types = TypeLibrary.GetTypes<IGameEventHandler<T>>().ToArray();\r\n\t\tvar helper = new SortingHelper( types.Length );\r\n\r\n\t\tfor ( var i = 0; i < types.Length; ++i )\r\n\t\t{\r\n\t\t\tvar type = types[i];\r\n\t\t\tvar method = GetImplementation<T>( type );\r\n\r\n\t\t\tif ( method is null )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\"Can't find {nameof( IGameEventHandler<T> )}<{typeof( T ).Name}> implementation in {type.Name}!\" );\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tforeach ( var attrib in method.Attributes )\r\n\t\t\t{\r\n\t\t\t\tswitch ( attrib )\r\n\t\t\t\t{\r\n\t\t\t\t\tcase EarlyAttribute:\r\n\t\t\t\t\t\thelper.AddFirst( i );\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase LateAttribute:\r\n\t\t\t\t\t\thelper.AddLast( i );\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase IBeforeAttribute before:\r\n\t\t\t\t\t\tfor ( var j = 0; j < types.Length; ++j )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif ( i == j ) continue;\r\n\r\n\t\t\t\t\t\t\tvar other = types[j];\r\n\r\n\t\t\t\t\t\t\tif ( before.Type.IsAssignableFrom( other.TargetType ) )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\thelper.AddConstraint( i, j );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase IAfterAttribute after:\r\n\t\t\t\t\t\tfor ( var j = 0; j < types.Length; ++j )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif ( i == j ) continue;\r\n\r\n\t\t\t\t\t\t\tvar other = types[j];\r\n\r\n\t\t\t\t\t\t\tif ( after.Type.IsAssignableFrom( other.TargetType ) )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\thelper.AddConstraint( j, i );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar ordering = new List<int>();\r\n\r\n\t\tif ( !helper.Sort( ordering, out var invalid ) )\r\n\t\t{\r\n\t\t\tLog.Error( $\"Invalid event ordering constraint between {types[invalid.EarlierIndex].Name} and {types[invalid.LaterIndex].Name}!\" );\r\n\t\t\treturn ImmutableDictionary<Type, int>.Empty;\r\n\t\t}\r\n\r\n\t\treturn Enumerable.Range( 0, ordering.Count )\r\n\t\t\t.ToImmutableDictionary( i => types[ordering[i]].TargetType, i => i );\r\n\t}\r\n}\r\n\r\npublic delegate void GameEventAction<in T>( T eventArgs )\r\n\twhere T : IGameEvent;\r\n\r\n/// <summary>\r\n/// Base class for components that expose game events to Action Graph.\r\n/// </summary>\r\npublic abstract class GameEventComponent<T> : Component, IGameEventHandler<T>\r\n\twhere T : IGameEvent\r\n{\r\n\t/// <summary>\r\n\t/// Action invoked when the <typeparamref name=\"T\"/> event is dispatched.\r\n\t/// </summary>\r\n\t[Property]\r\n\tpublic GameEventAction<T>? OnEvent { get; set; }\r\n\r\n\tvoid IGameEventHandler<T>.OnGameEvent( T eventArgs )\r\n\t{\r\n\t\tOnEvent?.Invoke( eventArgs );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.libevents",
            "Path": "Transition.cs",
            "FileName": "Transition.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 65480,
            "Code": "using System;\r\nusing Sandbox.Diagnostics;\r\n\r\nnamespace Sandbox.States;\r\n\r\npublic sealed class Transition : IComparable<Transition>, IValid\r\n{\r\n\tprivate float? _delay;\r\n\tprivate Func<bool>? _condition;\r\n\r\n\t/// <summary>\r\n\t/// The state machine containing this transition.\r\n\t/// </summary>\r\n\tpublic StateMachineComponent StateMachine => Source.StateMachine;\r\n\r\n\t/// <summary>\r\n\t/// Unique ID of this transition in the <see cref=\"StateMachineComponent\"/>.\r\n\t/// </summary>\r\n\tpublic int Id { get; }\r\n\r\n\t/// <summary>\r\n\t/// The state this transition originates from.\r\n\t/// </summary>\r\n\tpublic State Source { get; }\r\n\r\n\t/// <summary>\r\n\t/// The destination of this transition.\r\n\t/// </summary>\r\n\tpublic State Target { get; }\r\n\r\n\t/// <summary>\r\n\t/// Does this transition still belong to a state.\r\n\t/// </summary>\r\n\tpublic bool IsValid { get; internal set; }\r\n\r\n\tinternal Transition( int id, State source, State target )\r\n\t{\r\n\t\tSource = source;\r\n\t\tTarget = target;\r\n\t\tId = id;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Optional delay before this transition is taken.\r\n\t/// If null, this transition can be taken at any time.\r\n\t/// </summary>\r\n\tpublic float? Delay\r\n\t{\r\n\t\tget => _delay;\r\n\t\tset\r\n\t\t{\r\n\t\t\t_delay = value;\r\n\t\t\tSource.InvalidateTransitions();\r\n\t\t}\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Optional condition to evaluate.\r\n\t/// </summary>\r\n\tpublic Func<bool>? Condition\r\n\t{\r\n\t\tget => _condition;\r\n\t\tset\r\n\t\t{\r\n\t\t\t_condition = value;\r\n\t\t\tSource.InvalidateTransitions();\r\n\t\t}\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Action performed when this transition is taken.\r\n\t/// </summary>\r\n\tpublic Action? OnTransition { get; set; }\r\n\r\n\tpublic void Remove()\r\n\t{\r\n\t\tif ( !IsValid ) return;\r\n\t\tStateMachine.RemoveTransition( this );\r\n\t}\r\n\r\n\tpublic int CompareTo( Transition? other )\r\n\t{\r\n\t\tif ( other is null ) return 1;\r\n\r\n\t\tvar delayCompare = (Delay ?? float.PositiveInfinity).CompareTo( other.Delay ?? float.PositiveInfinity );\r\n\t\tif ( delayCompare != 0 ) return delayCompare;\r\n\r\n\t\tvar conditionCompare = (Condition is null).CompareTo( other.Condition is null );\r\n\t\tif ( conditionCompare != 0 ) return conditionCompare;\r\n\r\n\t\treturn Target.Id.CompareTo( other.Target.Id );\r\n\t}\r\n\r\n\tinternal record Model( int Id, int SourceId, int TargetId, float? Delay, Func<bool>? Condition, Action? OnTransition );\r\n\r\n\tinternal Model Serialize()\r\n\t{\r\n\t\treturn new Model( Id, Source.Id, Target.Id, Delay, Condition, OnTransition );\r\n\t}\r\n\r\n\tinternal void Deserialize( Model model )\r\n\t{\r\n\t\tAssert.AreEqual( Id, model.Id );\r\n\t\tAssert.AreEqual( Source.Id, model.SourceId );\r\n\t\tAssert.AreEqual( Target.Id, model.TargetId );\r\n\r\n\t\tDelay = model.Delay;\r\n\t\tCondition = model.Condition;\r\n\t\tOnTransition = model.OnTransition;\r\n\t}\r\n}\r\n"
        }
    ]
}