diff --git a/src/BizHawk.Emulation.Common/Base Implementations/BasicServiceProvider.cs b/src/BizHawk.Emulation.Common/Base Implementations/BasicServiceProvider.cs index 462582d82a..1b61370d96 100644 --- a/src/BizHawk.Emulation.Common/Base Implementations/BasicServiceProvider.cs +++ b/src/BizHawk.Emulation.Common/Base Implementations/BasicServiceProvider.cs @@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Common /// public class BasicServiceProvider : IEmulatorServiceProvider { - private readonly Dictionary _services = new Dictionary(); + private readonly Dictionary _services = [ ]; 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 // core doesn't implement directly needs to be added with Register() // 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) && t != typeof(IEmulatorService) && t != typeof(ISpecializedEmulatorService))) @@ -45,30 +45,32 @@ namespace BizHawk.Emulation.Common /// is null public void Register(T provider) where T : class, IEmulatorService - { - _services[typeof(T)] = provider; - } + => _services[typeof(T)] = provider; + + /// + /// 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 + /// + /// The to unregister + public void Unregister() + where T : class, IEmulatorService + => _services.Remove(typeof(T)); public T GetService() where T : IEmulatorService => (T) GetService(typeof(T))!; public object? GetService(Type t) - { - return _services.TryGetValue(t, out var service) ? service : null; - } + => _services.TryGetValue(t, out var service) ? service : null; public bool HasService() where T : IEmulatorService - { - return HasService(typeof(T)); - } + => HasService(typeof(T)); public bool HasService(Type t) - { - return _services.ContainsKey(t); - } + => _services.ContainsKey(t); - public IEnumerable AvailableServices =>_services.Select(d => d.Key); + public IEnumerable AvailableServices => _services.Select(d => d.Key); } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index 94a8cf434f..3a42ca6917 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -10,7 +10,7 @@ using BizHawk.Emulation.Cores.Components.M6502; namespace BizHawk.Emulation.Cores.Nintendo.NES { - public partial class NES : IEmulator, ISoundProvider/*, ICycleTiming*/ + public partial class NES : IEmulator, ISoundProvider, ICycleTiming { internal static class RomChecksums { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs index 4a952d00c6..636c650685 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs @@ -71,6 +71,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } } + // only the subframe core should have ICycleTiming registered + if (!subframe) + { + ser.Unregister(); + } + ResetControllerDefinition(subframe); } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.cs index c3e48990b1..301833d6b8 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.cs @@ -4,8 +4,7 @@ using BizHawk.Emulation.Cores.Nintendo.NES; namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk { [Core(CoreNames.SubNesHawk, "")] - public partial class SubNESHawk : IEmulator, IStatable, IInputPollable, - ISettable, ICycleTiming + public partial class SubNESHawk : IEmulator, IStatable, IInputPollable, ISettable { [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) @@ -36,6 +35,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk ser.Register(_nesCore.ServiceProvider.GetService()); ser.Register(_nesCore.ServiceProvider.GetService()); ser.Register(_nesCore.ServiceProvider.GetService()); + ser.Register(_nesCore.ServiceProvider.GetService()); 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); @@ -76,8 +76,5 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk public NES.NES.NESSyncSettings GetSyncSettings() => _nesCore.GetSyncSettings(); public PutSettingsDirtyBits PutSettings(NES.NES.NESSettings o) => _nesCore.PutSettings(o); public PutSettingsDirtyBits PutSyncSettings(NES.NES.NESSyncSettings o) => _nesCore.PutSyncSettings(o); - - public long CycleCount => _nesCore.CycleCount; - public double ClockRate => _nesCore.ClockRate; } } diff --git a/src/BizHawk.Emulation.Cores/Libretro/Libretro.IStatable.cs b/src/BizHawk.Emulation.Cores/Libretro/Libretro.IStatable.cs index eb19dfe6c9..ab708c4d72 100644 --- a/src/BizHawk.Emulation.Cores/Libretro/Libretro.IStatable.cs +++ b/src/BizHawk.Emulation.Cores/Libretro/Libretro.IStatable.cs @@ -5,40 +5,29 @@ using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Libretro { - // not all Libretro cores implement savestates - // we use this so we can optionally register IStatable - // todo: this can probably be genericized - public class StatableLibretro : IStatable + // note: Not all Libretro cores implement savestates + public partial class LibretroHost : IStatable { - private readonly LibretroHost _host; - private readonly LibretroApi _api; - private readonly byte[] _stateBuf; - - public StatableLibretro(LibretroHost host, LibretroApi api, int maxSize) - { - _host = host; - _api = api; - _stateBuf = new byte[maxSize]; - } + private byte[] _stateBuf = [ ]; public bool AvoidRewind => false; 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) { 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(_stateBuf, 0, len); // host variables - writer.Write(_host.Frame); - writer.Write(_host.LagCount); - writer.Write(_host.IsLagFrame); + writer.Write(Frame); + writer.Write(LagCount); + writer.Write(IsLagFrame); } 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!"); } - reader.Read(_stateBuf, 0, len); - _api.retro_unserialize(_stateBuf, len); + _ = reader.Read(_stateBuf, 0, len); + api.retro_unserialize(_stateBuf, len); // host variables - _host.Frame = reader.ReadInt32(); - _host.LagCount = reader.ReadInt32(); - _host.IsLagFrame = reader.ReadBoolean(); + Frame = reader.ReadInt32(); + LagCount = reader.ReadInt32(); + IsLagFrame = reader.ReadBoolean(); } } } diff --git a/src/BizHawk.Emulation.Cores/Libretro/Libretro.cs b/src/BizHawk.Emulation.Cores/Libretro/Libretro.cs index a6d163e5d0..a26033cce9 100644 --- a/src/BizHawk.Emulation.Cores/Libretro/Libretro.cs +++ b/src/BizHawk.Emulation.Cores/Libretro/Libretro.cs @@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Libretro // 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 [PortedCore(CoreNames.Libretro, "CasualPokePlayer", singleInstance: true, isReleased: false)] - [ServiceNotApplicable(new[] { typeof(IDriveLight) })] + [ServiceNotApplicable([ typeof(IDriveLight) ])] public partial class LibretroHost { private static readonly LibretroBridge bridge; @@ -28,32 +28,24 @@ namespace BizHawk.Emulation.Cores.Libretro } private readonly LibretroApi api; - private IStatable _stateWrapper; - 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 IntPtr _activeHandler; private static int _refCount; - private readonly IntPtr _parentHandler; - - public BridgeGuard(IntPtr parentHandler) - => _parentHandler = parentHandler; - public void Enter() { lock (_sync) { if (_activeHandler == IntPtr.Zero) { - _activeHandler = _parentHandler; - bridge.LibretroBridge_SetGlobalCallbackHandler(_parentHandler); + _activeHandler = parentHandler; + bridge.LibretroBridge_SetGlobalCallbackHandler(parentHandler); } - else if (_activeHandler != _parentHandler) + else if (_activeHandler != parentHandler) { 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!"); } - _guard = new(cbHandler); - + var guard = new BridgeGuard(cbHandler); api = BizInvoker.GetInvoker( - new DynamicLibraryImportResolver(corePath, hasLimitedLifetime: false), _guard, CallingConventionAdapters.Native); + new DynamicLibraryImportResolver(corePath, hasLimitedLifetime: false), guard, CallingConventionAdapters.Native); _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 long Length { get; } - - public RetroData(object o, long len = 0) - { - _handle = GCHandle.Alloc(o, GCHandleType.Pinned); - Length = len; - } + public long Length { get; } = len; public void Dispose() => _handle.Free(); } - private byte[] RetroString(string managedString) + private static byte[] RetroString(string managedString) { var ret = Encoding.UTF8.GetBytes(managedString); Array.Resize(ref ret, ret.Length + 1); @@ -205,11 +190,11 @@ namespace BizHawk.Emulation.Cores.Libretro success = api.retro_load_no_game(); break; case RETRO_LOAD.PATH: - game = new() { path = path.PinnedData }; + game = new() { path = path!.PinnedData }; success = api.retro_load_game(ref game); break; 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); break; default: @@ -237,8 +222,11 @@ namespace BizHawk.Emulation.Cores.Libretro var len = checked((int)api.retro_serialize_size()); if (len > 0) { - _stateWrapper = new StatableLibretro(this, api, len); - _serviceProvider.Register(_stateWrapper); + _stateBuf = new byte[len]; + } + else + { + _serviceProvider.Unregister(); } _region = api.retro_get_region();