BizHawk/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs

351 lines
9.6 KiB
C#

using BizHawk.Common;
using BizHawk.Common.BizInvoke;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Sound;
using BizHawk.Emulation.Cores.Waterbox;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Consoles.SNK
{
[Core("Dual NeoPop", "Thomas Klausner, Mednafen Team, natt", true, false, "0.9.44.1",
"https://mednafen.github.io/releases/", false)]
public class DualNeoGeoPort : IEmulator
{
private NeoGeoPort _left;
private NeoGeoPort _right;
private readonly BasicServiceProvider _serviceProvider;
private bool _disposed = false;
private readonly DualSyncSound _soundProvider;
private readonly SideBySideVideo _videoProvider;
private readonly LinkInterop _leftEnd;
private readonly LinkInterop _rightEnd;
private readonly LinkCable _linkCable;
[CoreConstructor("DNGP")]
public DualNeoGeoPort(CoreComm comm, byte[] rom, bool deterministic)
{
CoreComm = comm;
_left = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.CanonicalStart);
_right = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.AlternateStart);
_linkCable = new LinkCable();
_leftEnd = new LinkInterop(_left, _linkCable.LeftIn, _linkCable.LeftOut);
_rightEnd = new LinkInterop(_right, _linkCable.RightIn, _linkCable.RightOut);
_serviceProvider = new BasicServiceProvider(this);
_soundProvider = new DualSyncSound(_left, _right);
_serviceProvider.Register<ISoundProvider>(_soundProvider);
_videoProvider = new SideBySideVideo(_left, _right);
_serviceProvider.Register<IVideoProvider>(_videoProvider);
}
public bool FrameAdvance(IController controller, bool render, bool rendersound = true)
{
var t1 = Task.Run(() =>
{
_left.FrameAdvance(new PrefixController(controller, "P1 "), render, rendersound);
_leftEnd.SignalEndOfFrame();
});
var t2 = Task.Run(() =>
{
_right.FrameAdvance(new PrefixController(controller, "P2 "), render, rendersound);
_rightEnd.SignalEndOfFrame();
});
var t3 = Task.Run(() =>
{
_linkCable.RunFrame();
});
Task.WaitAll(t1, t2, t3);
Frame++;
_soundProvider.Fetch();
_videoProvider.Fetch();
return true;
}
#region link cable
private class LinkCable
{
public readonly BlockingCollection<LinkRequest> LeftIn = new BlockingCollection<LinkRequest>();
public readonly BlockingCollection<LinkResult> LeftOut = new BlockingCollection<LinkResult>();
public readonly BlockingCollection<LinkRequest> RightIn = new BlockingCollection<LinkRequest>();
public readonly BlockingCollection<LinkResult> RightOut = new BlockingCollection<LinkResult>();
private readonly Queue<byte> _leftData = new Queue<byte>();
private readonly Queue<byte> _rightData = new Queue<byte>();
public void RunFrame()
{
LinkRequest l = LeftIn.Take();
LinkRequest r = RightIn.Take();
while (true)
{
switch (l.RequestType)
{
case LinkRequest.RequestTypes.EndOfFrame:
if (r.RequestType == LinkRequest.RequestTypes.EndOfFrame)
{
Console.WriteLine("\nEnd of Frame {0} {1}", _leftData.Count, _rightData.Count);
return;
}
break;
case LinkRequest.RequestTypes.Write:
Console.Write("LW ");
_leftData.Enqueue(l.Data);
l = LeftIn.Take();
continue;
case LinkRequest.RequestTypes.Read:
case LinkRequest.RequestTypes.Poll:
if (_rightData.Count > 0)
{
if (l.RequestType == LinkRequest.RequestTypes.Read)
Console.Write("LR ");
LeftOut.Add(new LinkResult
{
Data = l.RequestType == LinkRequest.RequestTypes.Read ? _rightData.Dequeue() : _rightData.Peek(),
Return = true
});
l = LeftIn.Take();
continue;
}
else if (r.RequestType != LinkRequest.RequestTypes.Write)
{
if (l.RequestType == LinkRequest.RequestTypes.Read)
Console.Write("L! ");
LeftOut.Add(new LinkResult
{
Data = l.Data,
Return = false
});
l = LeftIn.Take();
continue;
}
else
{
break;
}
}
switch (r.RequestType)
{
case LinkRequest.RequestTypes.Write:
Console.Write("RW ");
_rightData.Enqueue(r.Data);
r = RightIn.Take();
continue;
case LinkRequest.RequestTypes.Read:
case LinkRequest.RequestTypes.Poll:
if (_leftData.Count > 0)
{
if (r.RequestType == LinkRequest.RequestTypes.Read)
Console.Write("RR ");
RightOut.Add(new LinkResult
{
Data = r.RequestType == LinkRequest.RequestTypes.Read ? _leftData.Dequeue() : _leftData.Peek(),
Return = true
});
r = RightIn.Take();
continue;
}
else if (l.RequestType != LinkRequest.RequestTypes.Write)
{
if (r.RequestType == LinkRequest.RequestTypes.Read)
Console.Write("R! ");
RightOut.Add(new LinkResult
{
Data = r.Data,
Return = false
});
r = RightIn.Take();
continue;
}
else
{
break;
}
}
}
}
}
public struct LinkRequest
{
public enum RequestTypes : byte
{
Read,
Poll,
Write,
EndOfFrame
}
public RequestTypes RequestType;
public byte Data;
}
public struct LinkResult
{
public byte Data;
public bool Return;
}
private unsafe class LinkInterop
{
private readonly BlockingCollection<LinkRequest> _push;
private readonly BlockingCollection<LinkResult> _pull;
private NeoGeoPort _core;
private readonly IntPtr _readcb;
private readonly IntPtr _pollcb;
private readonly IntPtr _writecb;
private readonly IImportResolver _exporter;
public LinkInterop(NeoGeoPort core, BlockingCollection<LinkRequest> push, BlockingCollection<LinkResult> pull)
{
_core = core;
_push = push;
_pull = pull;
_exporter = BizExvoker.GetExvoker(this, CallingConventionAdapters.Waterbox);
_readcb = _exporter.SafeResolve("CommsReadCallback");
_pollcb = _exporter.SafeResolve("CommsPollCallback");
_writecb = _exporter.SafeResolve("CommsWriteCallback");
ConnectPointers();
}
private void ConnectPointers()
{
_core._neopop.SetCommsCallbacks(_readcb, _pollcb, _writecb);
}
private bool CommsPollNoBuffer()
{
_push.Add(new LinkRequest
{
RequestType = LinkRequest.RequestTypes.Poll
});
return _pull.Take().Return;
}
[BizExport(CallingConvention.Cdecl)]
public bool CommsReadCallback(byte* buffer)
{
if (buffer == null)
return CommsPollNoBuffer();
_push.Add(new LinkRequest
{
RequestType = LinkRequest.RequestTypes.Read,
Data = *buffer
});
var r = _pull.Take();
*buffer = r.Data;
return r.Return;
}
[BizExport(CallingConvention.Cdecl)]
public bool CommsPollCallback(byte* buffer)
{
if (buffer == null)
return CommsPollNoBuffer();
_push.Add(new LinkRequest
{
RequestType = LinkRequest.RequestTypes.Poll,
Data = *buffer
});
var r = _pull.Take();
*buffer = r.Data;
return r.Return;
}
[BizExport(CallingConvention.Cdecl)]
public void CommsWriteCallback(byte data)
{
_push.Add(new LinkRequest
{
RequestType = LinkRequest.RequestTypes.Write,
Data = data
});
}
public void SignalEndOfFrame()
{
_push.Add(new LinkRequest
{
RequestType = LinkRequest.RequestTypes.EndOfFrame
});
}
public void PostLoadState()
{
ConnectPointers();
}
}
#endregion
private class PrefixController : IController
{
public PrefixController(IController controller, string prefix)
{
_controller = controller;
_prefix = prefix;
}
private readonly IController _controller;
private readonly string _prefix;
public ControllerDefinition Definition => null;
public float GetFloat(string name)
{
return _controller.GetFloat(_prefix + name);
}
public bool IsPressed(string button)
{
return _controller.IsPressed(_prefix + button);
}
}
public ControllerDefinition ControllerDefinition => DualNeoGeoPortController;
private static readonly ControllerDefinition DualNeoGeoPortController = new ControllerDefinition
{
BoolButtons =
{
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 A", "P1 B", "P1 Option", "P1 Power",
"P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 A", "P2 B", "P2 Option", "P2 Power"
},
Name = "Dual NeoGeo Portable Controller"
};
public void ResetCounters()
{
Frame = 0;
}
public int Frame { get; private set; }
public IEmulatorServiceProvider ServiceProvider => _serviceProvider;
public CoreComm CoreComm { get; }
public bool DeterministicEmulation => _left.DeterministicEmulation && _right.DeterministicEmulation;
public string SystemId => "DNGP";
public void Dispose()
{
if (!_disposed)
{
_left.Dispose();
_right.Dispose();
_left = null;
_right = null;
_disposed = true;
}
}
}
}