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}'"); } } }