PkmnLibSharp/PkmnLibSharp/Battling/Events/BattleEventListener.cs

103 lines
3.9 KiB
C#

using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace PkmnLibSharp.Battling.Events
{
public class BattleEventListener
{
public delegate Task BattleEventDelegate(EventData evt);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void BattleEventPtrDelegate(IntPtr ptr);
private readonly BattleEventDelegate _del;
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly BattleEventPtrDelegate _innerDel;
internal readonly IntPtr FunctionPtr;
private Task? _currentTask;
public BattleEventListener(BattleEventDelegate del)
{
_del = del;
_innerDel = OnEventPtr;
FunctionPtr = Marshal.GetFunctionPointerForDelegate(_innerDel);
}
public void EnsureFinishedListening()
{
try
{
_currentTask?.Wait();
}
catch (AggregateException e)
{
LogHandler.Log(LogHandler.LogLevel.Error, e.GetBaseException().Message);
}
catch (Exception e)
{
LogHandler.Log(LogHandler.LogLevel.Error, e.Message);
}
}
private void OnEventPtr(IntPtr ptr)
{
// If we're still registering another event, wait until that's done. This ensures events always arrive in
// the correct order.
EnsureFinishedListening();
// We do however want to run the event handler async, so that the battle code can keep running up till its
// next event.
_currentTask = Task.Run(() => InternalRunner(ptr));
}
private async Task InternalRunner(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return;
}
var wrapped = WrapEventPtr(ptr);
await _del.Invoke(wrapped);
}
private static EventData WrapEventPtr(IntPtr ptr)
{
var evtType = (EventDataKind)Creaturelib.Generated.EventData.GetKind(ptr);
switch (evtType)
{
case EventDataKind.Damage:
return new DamageEvent(evtType, ptr);
case EventDataKind.Heal:
return new HealEvent(evtType, ptr);
case EventDataKind.Faint:
return new FaintEvent(evtType, ptr);
case EventDataKind.Switch:
return new SwitchEvent(evtType, ptr);
case EventDataKind.TurnStart:
return new TurnStartEvent(evtType, ptr);
case EventDataKind.TurnEnd:
return new TurnEndEvent(evtType, ptr);
case EventDataKind.ExperienceGain:
return new ExperienceGainEvent(evtType, ptr);
case EventDataKind.DisplayText:
return new DisplayTextEvent(evtType, ptr);
case EventDataKind.Miss:
return new MissEvent(evtType, ptr);
case EventDataKind.ChangeSpecies:
return new ChangeSpeciesEvent(evtType, ptr);
case EventDataKind.ChangeVariant:
return new ChangeFormeEvent(evtType, ptr);
case EventDataKind.AttackUse:
return new MoveUseEvent(evtType, ptr);
case EventDataKind.ChangeStatBoost:
return new ChangeStatBoostEvent(evtType, ptr);
case EventDataKind.WeatherChange:
break;
case EventDataKind.StatusChange:
break;
default:
throw new ArgumentOutOfRangeException();
}
throw new NotImplementedException($"Unhandled battle event: '{evtType}'");
}
}
}