Add way to unregister auto-registered services with BasicServiceProvider

allows for better handling with SubNESHawk and Libretro statable, maybe others
This commit is contained in:
CasualPokePlayer 2024-06-16 21:18:24 -07:00
parent 145e0431d0
commit 87209aa11b
6 changed files with 57 additions and 75 deletions

View File

@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Common
/// <seealso cref="IEmulatorServiceProvider"/> /// <seealso cref="IEmulatorServiceProvider"/>
public class BasicServiceProvider : IEmulatorServiceProvider public class BasicServiceProvider : IEmulatorServiceProvider
{ {
private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>(); private readonly Dictionary<Type, object> _services = [ ];
public BasicServiceProvider(IEmulator core) public BasicServiceProvider(IEmulator core)
{ {
@ -23,7 +23,7 @@ namespace BizHawk.Emulation.Common
// find the field), but we're going to keep such logic out of the basic provider. anything the passed // find the field), but we're going to keep such logic out of the basic provider. anything the passed
// core doesn't implement directly needs to be added with Register() // core doesn't implement directly needs to be added with Register()
// this also fully allows services that are not IEmulatorService // this also fully allows services that are not IEmulatorService
Type coreType = core.GetType(); var coreType = core.GetType();
foreach (var service in coreType.GetInterfaces().Where(static t => typeof(IEmulatorService).IsAssignableFrom(t) foreach (var service in coreType.GetInterfaces().Where(static t => typeof(IEmulatorService).IsAssignableFrom(t)
&& t != typeof(IEmulatorService) && t != typeof(ISpecializedEmulatorService))) && t != typeof(IEmulatorService) && t != typeof(ISpecializedEmulatorService)))
@ -45,30 +45,32 @@ namespace BizHawk.Emulation.Common
/// <exception cref="ArgumentNullException"><paramref name="provider"/> is null</exception> /// <exception cref="ArgumentNullException"><paramref name="provider"/> is null</exception>
public void Register<T>(T provider) public void Register<T>(T provider)
where T : class, IEmulatorService where T : class, IEmulatorService
{ => _services[typeof(T)] = provider;
_services[typeof(T)] = provider;
} /// <summary>
/// the core can call this to unregister an existing service
/// this is particularly useful wrt the auto-registration of services
/// in case the core has some condition which renders a service unusable
/// </summary>
/// <typeparam name="T">The <see cref="IEmulatorService"/> to unregister</typeparam>
public void Unregister<T>()
where T : class, IEmulatorService
=> _services.Remove(typeof(T));
public T GetService<T>() public T GetService<T>()
where T : IEmulatorService where T : IEmulatorService
=> (T) GetService(typeof(T))!; => (T) GetService(typeof(T))!;
public object? GetService(Type t) public object? GetService(Type t)
{ => _services.TryGetValue(t, out var service) ? service : null;
return _services.TryGetValue(t, out var service) ? service : null;
}
public bool HasService<T>() public bool HasService<T>()
where T : IEmulatorService where T : IEmulatorService
{ => HasService(typeof(T));
return HasService(typeof(T));
}
public bool HasService(Type t) public bool HasService(Type t)
{ => _services.ContainsKey(t);
return _services.ContainsKey(t);
}
public IEnumerable<Type> AvailableServices =>_services.Select(d => d.Key); public IEnumerable<Type> AvailableServices => _services.Select(d => d.Key);
} }
} }

View File

@ -10,7 +10,7 @@ using BizHawk.Emulation.Cores.Components.M6502;
namespace BizHawk.Emulation.Cores.Nintendo.NES namespace BizHawk.Emulation.Cores.Nintendo.NES
{ {
public partial class NES : IEmulator, ISoundProvider/*, ICycleTiming*/ public partial class NES : IEmulator, ISoundProvider, ICycleTiming
{ {
internal static class RomChecksums internal static class RomChecksums
{ {

View File

@ -71,6 +71,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
} }
} }
// only the subframe core should have ICycleTiming registered
if (!subframe)
{
ser.Unregister<ICycleTiming>();
}
ResetControllerDefinition(subframe); ResetControllerDefinition(subframe);
} }

View File

@ -4,8 +4,7 @@ using BizHawk.Emulation.Cores.Nintendo.NES;
namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk
{ {
[Core(CoreNames.SubNesHawk, "")] [Core(CoreNames.SubNesHawk, "")]
public partial class SubNESHawk : IEmulator, IStatable, IInputPollable, public partial class SubNESHawk : IEmulator, IStatable, IInputPollable, ISettable<NES.NES.NESSettings, NES.NES.NESSyncSettings>
ISettable<NES.NES.NESSettings, NES.NES.NESSyncSettings>, ICycleTiming
{ {
[CoreConstructor(VSystemID.Raw.NES, Priority = CorePriority.SuperLow)] [CoreConstructor(VSystemID.Raw.NES, Priority = CorePriority.SuperLow)]
public SubNESHawk(CoreComm comm, GameInfo game, byte[] rom, /*string gameDbFn,*/ NES.NES.NESSettings settings, NES.NES.NESSyncSettings syncSettings) public SubNESHawk(CoreComm comm, GameInfo game, byte[] rom, /*string gameDbFn,*/ NES.NES.NESSettings settings, NES.NES.NESSyncSettings syncSettings)
@ -36,6 +35,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk
ser.Register(_nesCore.ServiceProvider.GetService<IRegionable>()); ser.Register(_nesCore.ServiceProvider.GetService<IRegionable>());
ser.Register(_nesCore.ServiceProvider.GetService<ICodeDataLogger>()); ser.Register(_nesCore.ServiceProvider.GetService<ICodeDataLogger>());
ser.Register(_nesCore.ServiceProvider.GetService<IDriveLight>()); ser.Register(_nesCore.ServiceProvider.GetService<IDriveLight>());
ser.Register(_nesCore.ServiceProvider.GetService<ICycleTiming>());
const string TRACE_HEADER = "6502: PC, machine code, mnemonic, operands, registers (A, X, Y, P, SP), flags (NVTBDIZCR), CPU Cycle, PPU Cycle"; const string TRACE_HEADER = "6502: PC, machine code, mnemonic, operands, registers (A, X, Y, P, SP), flags (NVTBDIZCR), CPU Cycle, PPU Cycle";
_tracer = new TraceBuffer(TRACE_HEADER); _tracer = new TraceBuffer(TRACE_HEADER);
@ -76,8 +76,5 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk
public NES.NES.NESSyncSettings GetSyncSettings() => _nesCore.GetSyncSettings(); public NES.NES.NESSyncSettings GetSyncSettings() => _nesCore.GetSyncSettings();
public PutSettingsDirtyBits PutSettings(NES.NES.NESSettings o) => _nesCore.PutSettings(o); public PutSettingsDirtyBits PutSettings(NES.NES.NESSettings o) => _nesCore.PutSettings(o);
public PutSettingsDirtyBits PutSyncSettings(NES.NES.NESSyncSettings o) => _nesCore.PutSyncSettings(o); public PutSettingsDirtyBits PutSyncSettings(NES.NES.NESSyncSettings o) => _nesCore.PutSyncSettings(o);
public long CycleCount => _nesCore.CycleCount;
public double ClockRate => _nesCore.ClockRate;
} }
} }

View File

@ -5,40 +5,29 @@ using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Libretro namespace BizHawk.Emulation.Cores.Libretro
{ {
// not all Libretro cores implement savestates // note: Not all Libretro cores implement savestates
// we use this so we can optionally register IStatable public partial class LibretroHost : IStatable
// todo: this can probably be genericized
public class StatableLibretro : IStatable
{ {
private readonly LibretroHost _host; private byte[] _stateBuf = [ ];
private readonly LibretroApi _api;
private readonly byte[] _stateBuf;
public StatableLibretro(LibretroHost host, LibretroApi api, int maxSize)
{
_host = host;
_api = api;
_stateBuf = new byte[maxSize];
}
public bool AvoidRewind => false; public bool AvoidRewind => false;
public void SaveStateBinary(BinaryWriter writer) public void SaveStateBinary(BinaryWriter writer)
{ {
var len = checked((int)_api.retro_serialize_size()); var len = checked((int)api.retro_serialize_size());
if (len > _stateBuf.Length) if (len > _stateBuf.Length)
{ {
throw new Exception("Core attempted to grow state size. This is not allowed per the libretro API."); throw new Exception("Core attempted to grow state size. This is not allowed per the libretro API.");
} }
_api.retro_serialize(_stateBuf, len); api.retro_serialize(_stateBuf, len);
writer.Write(len); writer.Write(len);
writer.Write(_stateBuf, 0, len); writer.Write(_stateBuf, 0, len);
// host variables // host variables
writer.Write(_host.Frame); writer.Write(Frame);
writer.Write(_host.LagCount); writer.Write(LagCount);
writer.Write(_host.IsLagFrame); writer.Write(IsLagFrame);
} }
public void LoadStateBinary(BinaryReader reader) public void LoadStateBinary(BinaryReader reader)
@ -49,13 +38,13 @@ namespace BizHawk.Emulation.Cores.Libretro
throw new Exception("State buffer size exceeded the core's maximum state size!"); throw new Exception("State buffer size exceeded the core's maximum state size!");
} }
reader.Read(_stateBuf, 0, len); _ = reader.Read(_stateBuf, 0, len);
_api.retro_unserialize(_stateBuf, len); api.retro_unserialize(_stateBuf, len);
// host variables // host variables
_host.Frame = reader.ReadInt32(); Frame = reader.ReadInt32();
_host.LagCount = reader.ReadInt32(); LagCount = reader.ReadInt32();
_host.IsLagFrame = reader.ReadBoolean(); IsLagFrame = reader.ReadBoolean();
} }
} }
} }

View File

@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Libretro
// nb: multiple libretro cores could theoretically be ran at once // nb: multiple libretro cores could theoretically be ran at once
// but all of them would need to be different cores, a core itself is single instance // but all of them would need to be different cores, a core itself is single instance
[PortedCore(CoreNames.Libretro, "CasualPokePlayer", singleInstance: true, isReleased: false)] [PortedCore(CoreNames.Libretro, "CasualPokePlayer", singleInstance: true, isReleased: false)]
[ServiceNotApplicable(new[] { typeof(IDriveLight) })] [ServiceNotApplicable([ typeof(IDriveLight) ])]
public partial class LibretroHost public partial class LibretroHost
{ {
private static readonly LibretroBridge bridge; private static readonly LibretroBridge bridge;
@ -28,32 +28,24 @@ namespace BizHawk.Emulation.Cores.Libretro
} }
private readonly LibretroApi api; private readonly LibretroApi api;
private IStatable _stateWrapper;
private readonly IntPtr cbHandler; private readonly IntPtr cbHandler;
private readonly BridgeGuard _guard;
private class BridgeGuard : IMonitor private class BridgeGuard(IntPtr parentHandler) : IMonitor
{ {
private static readonly object _sync = new(); private static readonly object _sync = new();
private static IntPtr _activeHandler; private static IntPtr _activeHandler;
private static int _refCount; private static int _refCount;
private readonly IntPtr _parentHandler;
public BridgeGuard(IntPtr parentHandler)
=> _parentHandler = parentHandler;
public void Enter() public void Enter()
{ {
lock (_sync) lock (_sync)
{ {
if (_activeHandler == IntPtr.Zero) if (_activeHandler == IntPtr.Zero)
{ {
_activeHandler = _parentHandler; _activeHandler = parentHandler;
bridge.LibretroBridge_SetGlobalCallbackHandler(_parentHandler); bridge.LibretroBridge_SetGlobalCallbackHandler(parentHandler);
} }
else if (_activeHandler != _parentHandler) else if (_activeHandler != parentHandler)
{ {
throw new InvalidOperationException("Multiple callback handlers cannot be active at once!"); throw new InvalidOperationException("Multiple callback handlers cannot be active at once!");
} }
@ -94,10 +86,9 @@ namespace BizHawk.Emulation.Cores.Libretro
throw new Exception("Failed to create callback handler!"); throw new Exception("Failed to create callback handler!");
} }
_guard = new(cbHandler); var guard = new BridgeGuard(cbHandler);
api = BizInvoker.GetInvoker<LibretroApi>( api = BizInvoker.GetInvoker<LibretroApi>(
new DynamicLibraryImportResolver(corePath, hasLimitedLifetime: false), _guard, CallingConventionAdapters.Native); new DynamicLibraryImportResolver(corePath, hasLimitedLifetime: false), guard, CallingConventionAdapters.Native);
_serviceProvider = new(this); _serviceProvider = new(this);
@ -140,23 +131,17 @@ namespace BizHawk.Emulation.Cores.Libretro
} }
} }
private class RetroData : IDisposable private class RetroData(object o, long len = 0) : IDisposable
{ {
private readonly GCHandle _handle; private GCHandle _handle = GCHandle.Alloc(o, GCHandleType.Pinned);
public IntPtr PinnedData => _handle.AddrOfPinnedObject(); public IntPtr PinnedData => _handle.AddrOfPinnedObject();
public long Length { get; } public long Length { get; } = len;
public RetroData(object o, long len = 0)
{
_handle = GCHandle.Alloc(o, GCHandleType.Pinned);
Length = len;
}
public void Dispose() => _handle.Free(); public void Dispose() => _handle.Free();
} }
private byte[] RetroString(string managedString) private static byte[] RetroString(string managedString)
{ {
var ret = Encoding.UTF8.GetBytes(managedString); var ret = Encoding.UTF8.GetBytes(managedString);
Array.Resize(ref ret, ret.Length + 1); Array.Resize(ref ret, ret.Length + 1);
@ -205,11 +190,11 @@ namespace BizHawk.Emulation.Cores.Libretro
success = api.retro_load_no_game(); success = api.retro_load_no_game();
break; break;
case RETRO_LOAD.PATH: case RETRO_LOAD.PATH:
game = new() { path = path.PinnedData }; game = new() { path = path!.PinnedData };
success = api.retro_load_game(ref game); success = api.retro_load_game(ref game);
break; break;
case RETRO_LOAD.DATA: case RETRO_LOAD.DATA:
game = new() { path = path.PinnedData, data = data.PinnedData, size = data.Length }; game = new() { path = path!.PinnedData, data = data!.PinnedData, size = data.Length };
success = api.retro_load_game(ref game); success = api.retro_load_game(ref game);
break; break;
default: default:
@ -237,8 +222,11 @@ namespace BizHawk.Emulation.Cores.Libretro
var len = checked((int)api.retro_serialize_size()); var len = checked((int)api.retro_serialize_size());
if (len > 0) if (len > 0)
{ {
_stateWrapper = new StatableLibretro(this, api, len); _stateBuf = new byte[len];
_serviceProvider.Register(_stateWrapper); }
else
{
_serviceProvider.Unregister<IStatable>();
} }
_region = api.retro_get_region(); _region = api.retro_get_region();