diff --git a/Library/DirectSound.cs b/Library/DirectSound.cs new file mode 100644 index 0000000..50f378c --- /dev/null +++ b/Library/DirectSound.cs @@ -0,0 +1,189 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Jellyfish.Library +{ + public sealed class DirectSoundUpdateEventArgs : EventArgs + { + private DirectSoundUpdateEventArgs() + { + } + + public static DirectSoundUpdateEventArgs Create(IntPtr buffer, int bufferSize) + { + _instance.Buffer = buffer; + _instance.BufferSize = bufferSize; + + return _instance; // use singleton; avoids garbage + } + + public IntPtr Buffer { get; private set; } + public int BufferSize { get; private set; } + + private static readonly DirectSoundUpdateEventArgs _instance = new DirectSoundUpdateEventArgs(); + } + + public sealed partial class DirectSound : IDisposable + { + public DirectSound(int sampleRate, int sampleChannels, int sampleBits, int sampleSize) + { + _sampleRate = sampleRate; + _sampleChannels = sampleChannels; + _sampleBits = sampleBits; + _sampleSize = sampleSize; + + _thread = new Thread(Run) { Name = "DirectSound" }; + } + + public void Dispose() + { + _position1Event.Close(); + _position2Event.Close(); + _stopEvent.Close(); + } + + public void Start(IntPtr window) + { + _window = window; + _thread.Start(); + } + + public void Stop() + { + _stopEvent.Set(); + _thread.Join(); + } + + [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")] + private void Initialize() + { + int hresult = NativeMethods.DirectSoundCreate(IntPtr.Zero, out _device, IntPtr.Zero); + if (hresult < 0) + { + Marshal.ThrowExceptionForHR(hresult); + } + + _device.SetCooperativeLevel(_window, CooperativeLevel.Normal); + + GCHandleHelpers.Pin(new WaveFormat(_sampleRate, _sampleChannels, _sampleBits), waveFormat => + { + BufferDescription description = new BufferDescription(BufferCapabilities.CtrlPositionNotify, BlockCount * _sampleSize, waveFormat); + _device.CreateSoundBuffer(description, out _buffer, IntPtr.Zero); + }); + ClearBuffer(); + + BufferPositionNotify[] positionEvents = new BufferPositionNotify[BlockCount] + { + new BufferPositionNotify(0 * _sampleSize, _position1Event.SafeWaitHandle.DangerousGetHandle()), + new BufferPositionNotify(1 * _sampleSize, _position2Event.SafeWaitHandle.DangerousGetHandle()) + }; + ((IDirectSoundNotify)_buffer).SetNotificationPositions(positionEvents.Length, positionEvents); + + _buffer.Play(0, 0, BufferPlay.Looping); + } + + private void ClearBuffer() + { + UpdateBuffer(0, 0, BufferLock.EntireBuffer, (buffer, bufferSize) => + { + MarshalHelpers.ZeroMemory(buffer, bufferSize); + }); + } + + private void RestoreBuffer() + { + BufferStatus status; + _buffer.GetStatus(out status); + if ((status & BufferStatus.BufferLost) != 0) + { + _buffer.Restore(); + } + } + + private void UpdateBuffer(int block) + { + EventHandler handler = Update; + if (handler != null) + { + UpdateBuffer(block * _sampleSize, _sampleSize, BufferLock.None, (buffer, bufferSize) => + { + handler(this, DirectSoundUpdateEventArgs.Create(buffer, bufferSize)); + }); + } + } + + private void UpdateBuffer(int offset, int count, BufferLock flags, Action updateBuffer) + { + RestoreBuffer(); + + IntPtr buffer1, buffer2; + int buffer1Size, buffer2Size; + _buffer.Lock(offset, count, out buffer1, out buffer1Size, out buffer2, out buffer2Size, flags); + try + { + if (buffer1 != IntPtr.Zero) + { + updateBuffer(buffer1, buffer1Size); + } + if (buffer2 != IntPtr.Zero) + { + updateBuffer(buffer2, buffer2Size); + } + } + finally + { + _buffer.Unlock(buffer1, buffer1Size, buffer2, buffer2Size); + } + } + + private void Uninitialize() + { + if (_buffer != null) + { + _buffer.Stop(); + Marshal.ReleaseComObject(_buffer); + } + if (_device != null) + { + Marshal.ReleaseComObject(_device); + } + } + + private void Run() // com mta thread + { + Initialize(); + + EventWaitHandle[] eventHandles = new EventWaitHandle[] { _position1Event, _position2Event, _stopEvent }; + int index = WaitHandle.WaitAny(eventHandles); + + while (index < BlockCount) + { + UpdateBuffer((index + 1) % BlockCount); // update next block in circular buffer + index = WaitHandle.WaitAny(eventHandles); + } + + Uninitialize(); + } + + public event EventHandler Update; + + private const int BlockCount = 2; + private const int WaveFormatPcm = 1; + + private int _sampleRate; + private int _sampleChannels; + private int _sampleBits; + private int _sampleSize; + + private Thread _thread; + private IntPtr _window; + private IDirectSound _device; + private IDirectSoundBuffer _buffer; + + private AutoResetEvent _position1Event = new AutoResetEvent(false); + private AutoResetEvent _position2Event = new AutoResetEvent(false); + private ManualResetEvent _stopEvent = new ManualResetEvent(false); + } +} diff --git a/Library/DirectSoundInterop.cs b/Library/DirectSoundInterop.cs new file mode 100644 index 0000000..8391208 --- /dev/null +++ b/Library/DirectSoundInterop.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Jellyfish.Library +{ + public sealed partial class DirectSound + { + [Flags] + private enum BufferCapabilities + { + PrimaryBuffer = 0x00000001, + CtrlPositionNotify = 0x00000100, + StickyFocus = 0x00004000, + GlobalFocus = 0x00008000 + } + + [Flags] + private enum BufferLock + { + None = 0x00000000, + FromWriteCursor = 0x00000001, + EntireBuffer = 0x00000002 + } + + [Flags] + private enum BufferPlay + { + Looping = 0x00000001 + } + + [Flags] + private enum BufferStatus + { + Playing = 0x00000001, + BufferLost = 0x00000002, + Looping = 0x00000004, + Terminated = 0x00000020 + } + + private enum CooperativeLevel + { + Normal = 1, + Priority = 2 + } + + [StructLayout(LayoutKind.Sequential)] + private sealed class BufferDescription + { + public BufferDescription(BufferCapabilities capabilities, int size, IntPtr format) + { + dwSize = Marshal.SizeOf(typeof(BufferDescription)); + dwFlags = capabilities; + dwBufferBytes = size; + lpwfxFormat = format; + } + + public int dwSize; + public BufferCapabilities dwFlags; + public int dwBufferBytes; + public int dwReserved; + public IntPtr lpwfxFormat; + public Guid guid3DAlgorithm; + } + + [StructLayout(LayoutKind.Sequential)] + private struct BufferPositionNotify + { + public BufferPositionNotify(int offset, IntPtr notifyEvent) + { + dwOffset = offset; + hEventNotify = notifyEvent; + } + + public int dwOffset; + public IntPtr hEventNotify; + } + + [StructLayout(LayoutKind.Sequential)] + private sealed class WaveFormat + { + public WaveFormat(int sampleRate, int sampleChannels, int sampleBits) + { + wFormatTag = WaveFormatPcm; + nSamplesPerSec = sampleRate; + nChannels = (short)sampleChannels; + wBitsPerSample = (short)sampleBits; + nBlockAlign = (short)(sampleChannels * sampleBits / 8); + nAvgBytesPerSec = sampleRate * nBlockAlign; + } + + public short wFormatTag; + public short nChannels; + public int nSamplesPerSec; + public int nAvgBytesPerSec; + public short nBlockAlign; + public short wBitsPerSample; + public short cbSize; + } + + [ComImport, Guid("279AFA83-4981-11CE-A521-0020AF0BE560"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IDirectSound + { + void CreateSoundBuffer(BufferDescription pcDSBufferDesc, [MarshalAs(UnmanagedType.Interface)] out IDirectSoundBuffer pDSBuffer, IntPtr pUnkOuter); + void GetCaps(IntPtr pDSCaps); + void DuplicateSoundBuffer([MarshalAs(UnmanagedType.Interface)] IDirectSoundBuffer pDSBufferOriginal, [MarshalAs(UnmanagedType.Interface)] out IDirectSoundBuffer pDSBufferDuplicate); + void SetCooperativeLevel(IntPtr hwnd, CooperativeLevel dwLevel); + void Compact(); + void GetSpeakerConfig(out int dwSpeakerConfig); + void SetSpeakerConfig(int dwSpeakerConfig); + void Initialize([MarshalAs(UnmanagedType.LPStruct)] Guid pcGuidDevice); + } + + [ComImport, Guid("279AFA85-4981-11CE-A521-0020AF0BE560"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IDirectSoundBuffer + { + void GetCaps(IntPtr pDSBufferCaps); + void GetCurrentPosition(out int dwCurrentPlayCursor, out int dwCurrentWriteCursor); + void GetFormat(IntPtr pwfxFormat, int dwSizeAllocated, out int dwSizeWritten); + void GetVolume(out int lVolume); + void GetPan(out int lPan); + void GetFrequency(out int dwFrequency); + void GetStatus(out BufferStatus dwStatus); + void Initialize([MarshalAs(UnmanagedType.Interface)] IDirectSound pDirectSound, BufferDescription pcDSBufferDesc); + void Lock(int dwOffset, int dwBytes, out IntPtr pvAudioPtr1, out int dwAudioBytes1, out IntPtr pvAudioPtr2, out int dwAudioBytes2, BufferLock dwFlags); + void Play(int dwReserved1, int dwPriority, BufferPlay dwFlags); + void SetCurrentPosition(int dwNewPosition); + void SetFormat(WaveFormat pcfxFormat); + void SetVolume(int lVolume); + void SetPan(int lPan); + void SetFrequency(int dwFrequency); + void Stop(); + void Unlock(IntPtr pvAudioPtr1, int dwAudioBytes1, IntPtr pvAudioPtr2, int dwAudioBytes2); + void Restore(); + } + + [ComImport, Guid("B0210783-89CD-11D0-AF08-00A0C925CD16"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IDirectSoundNotify + { + void SetNotificationPositions(int dwPositionNotifies, [MarshalAs(UnmanagedType.LPArray)] BufferPositionNotify[] pcPositionNotifies); + } + + [SuppressUnmanagedCodeSecurity] + private static class NativeMethods + { + [DllImport("dsound.dll")] + public static extern int DirectSoundCreate(IntPtr pcGuidDevice, [MarshalAs(UnmanagedType.Interface)] out IDirectSound pDS, IntPtr pUnkOuter); + } + } +} diff --git a/Library/FileHelpers.cs b/Library/FileHelpers.cs deleted file mode 100644 index 758d3c1..0000000 --- a/Library/FileHelpers.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; - -namespace Jellyfish.Library -{ - public static class FileHelpers - { - public static byte[] ReadAllBytes(string path) - { -#if SILVERLIGHT || XBOX || ZUNE - using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - return stream.ReadAllBytes(); - } -#else - return File.ReadAllBytes(path); -#endif - } - } -} diff --git a/Library/GCHandleHelpers.cs b/Library/GCHandleHelpers.cs new file mode 100644 index 0000000..91d3993 --- /dev/null +++ b/Library/GCHandleHelpers.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; +using System.Security.Permissions; + +namespace Jellyfish.Library +{ + public static class GCHandleHelpers + { + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + public static void Pin(object value, Action action) + { + GCHandle gcHandle = new GCHandle(); + try + { + gcHandle = GCHandle.Alloc(value, GCHandleType.Pinned); + action(gcHandle.AddrOfPinnedObject()); + } + finally + { + if (gcHandle.IsAllocated) + { + gcHandle.Free(); + } + } + } + } +} diff --git a/Library/GeneralSecurity.cs b/Library/GeneralSecurity.cs index e53b52c..603c189 100644 --- a/Library/GeneralSecurity.cs +++ b/Library/GeneralSecurity.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Security.AccessControl; +using System.Security.Permissions; using System.Security.Principal; namespace Jellyfish.Library @@ -9,6 +10,7 @@ namespace Jellyfish.Library [StructLayout(LayoutKind.Sequential)] public sealed class SecurityAttributes { + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public SecurityAttributes() { _length = Marshal.SizeOf(typeof(SecurityAttributes)); @@ -116,31 +118,21 @@ public void AddAuditRule(GeneralAuditRule rule) base.AddAuditRule(rule); } + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public void GetSecurityAttributes(bool inheritable, Action action) { GetSecurityAttributes(this, inheritable, action); } + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public static void GetSecurityAttributes(ObjectSecurity security, bool inheritable, Action action) { if (security != null) { - GCHandle gcHandle = new GCHandle(); - try + GCHandleHelpers.Pin(security.GetSecurityDescriptorBinaryForm(), securityDescriptor => { - gcHandle = GCHandle.Alloc(security.GetSecurityDescriptorBinaryForm(), GCHandleType.Pinned); - SecurityAttributes securityAttributes = new SecurityAttributes(); - securityAttributes.SecurityDescriptor = gcHandle.AddrOfPinnedObject(); - securityAttributes.InheritHandle = inheritable; - action(securityAttributes); - } - finally - { - if (gcHandle.IsAllocated) - { - gcHandle.Free(); - } - } + action(new SecurityAttributes() { SecurityDescriptor = securityDescriptor, InheritHandle = inheritable }); + }); } else if (inheritable) { diff --git a/Library/Lazy.cs b/Library/Lazy.cs index 6440d90..492e875 100644 --- a/Library/Lazy.cs +++ b/Library/Lazy.cs @@ -3,7 +3,7 @@ namespace Jellyfish.Library { - public class Lazy where T : class + public sealed class Lazy where T : class { public Lazy(Func initializer) { diff --git a/Library/MarshalHelpers.cs b/Library/MarshalHelpers.cs new file mode 100644 index 0000000..5f03978 --- /dev/null +++ b/Library/MarshalHelpers.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; + +namespace Jellyfish.Library +{ + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + public static class MarshalHelpers + { + public static void FillMemory(IntPtr buffer, int bufferSize, byte value) + { + NativeMethods.FillMemory(buffer, (IntPtr)bufferSize, value); + } + + public static void ZeroMemory(IntPtr buffer, int bufferSize) + { + NativeMethods.ZeroMemory(buffer, (IntPtr)bufferSize); + } + + [SuppressUnmanagedCodeSecurity] + private static class NativeMethods + { + [DllImport("kernel32.dll", SetLastError = true)] + public static extern void FillMemory(IntPtr destination, IntPtr length, byte fill); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern void ZeroMemory(IntPtr destination, IntPtr length); + } + } +} diff --git a/Library/Silverlight/ApplicationBase.cs b/Library/Silverlight/ApplicationBase.cs index a98c75d..f090b83 100644 --- a/Library/Silverlight/ApplicationBase.cs +++ b/Library/Silverlight/ApplicationBase.cs @@ -6,11 +6,6 @@ namespace Jellyfish.Library { public class ApplicationBase : Application { - public ApplicationBase() : - this(null) - { - } - public ApplicationBase(string name) { Name = name; diff --git a/Library/Silverlight/FrameRateCounter.xaml.cs b/Library/Silverlight/FrameRateCounter.xaml.cs index 5ac6961..bbc650c 100644 --- a/Library/Silverlight/FrameRateCounter.xaml.cs +++ b/Library/Silverlight/FrameRateCounter.xaml.cs @@ -5,7 +5,7 @@ namespace Jellyfish.Library { - public partial class FrameRateCounter : UserControl + public sealed partial class FrameRateCounter : UserControl { public FrameRateCounter() { diff --git a/Library/Silverlight/Jellyfish.Library.Silverlight.csproj b/Library/Silverlight/Jellyfish.Library.Silverlight.csproj index 2517581..393aa64 100644 --- a/Library/Silverlight/Jellyfish.Library.Silverlight.csproj +++ b/Library/Silverlight/Jellyfish.Library.Silverlight.csproj @@ -54,9 +54,6 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs - GlobalSuppressions.cs diff --git a/Library/Silverlight/StringFormatConverter.cs b/Library/Silverlight/StringFormatConverter.cs index 7c3b096..ecb203a 100644 --- a/Library/Silverlight/StringFormatConverter.cs +++ b/Library/Silverlight/StringFormatConverter.cs @@ -5,7 +5,7 @@ namespace Jellyfish.Library { - public class StringFormatConverter : IValueConverter // SL is missing Binding.StringFormat + public sealed class StringFormatConverter : IValueConverter // SL is missing Binding.StringFormat { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/Library/Wpf/ApplicationBase.cs b/Library/Wpf/ApplicationBase.cs index 2284e7d..e3ddfb5 100644 --- a/Library/Wpf/ApplicationBase.cs +++ b/Library/Wpf/ApplicationBase.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Security.Permissions; using System.Text; using System.Windows; using System.Windows.Threading; @@ -8,11 +9,7 @@ namespace Jellyfish.Library { public class ApplicationBase : Application { - public ApplicationBase() : - this(null) - { - } - + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public ApplicationBase(string name) { Name = name; diff --git a/Library/Wpf/FrameRateCounter.xaml.cs b/Library/Wpf/FrameRateCounter.xaml.cs index 5ac6961..bbc650c 100644 --- a/Library/Wpf/FrameRateCounter.xaml.cs +++ b/Library/Wpf/FrameRateCounter.xaml.cs @@ -5,7 +5,7 @@ namespace Jellyfish.Library { - public partial class FrameRateCounter : UserControl + public sealed partial class FrameRateCounter : UserControl { public FrameRateCounter() { diff --git a/Library/Wpf/Jellyfish.Library.Wpf.csproj b/Library/Wpf/Jellyfish.Library.Wpf.csproj index 2b4cc4b..fd486a2 100644 --- a/Library/Wpf/Jellyfish.Library.Wpf.csproj +++ b/Library/Wpf/Jellyfish.Library.Wpf.csproj @@ -62,8 +62,14 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs + + DirectSound.cs + + + DirectSoundInterop.cs + + + GCHandleHelpers.cs GeneralSecurity.cs @@ -77,6 +83,9 @@ Lazy.cs + + MarshalHelpers.cs + MathHelpers.cs @@ -100,6 +109,7 @@ FrameRateCounter.xaml + diff --git a/Library/Wpf/WindowExtensions.cs b/Library/Wpf/WindowExtensions.cs new file mode 100644 index 0000000..34b0fec --- /dev/null +++ b/Library/Wpf/WindowExtensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Windows; +using System.Windows.Interop; + +namespace Jellyfish.Library +{ + public static class WindowExtensions + { + public static IntPtr GetHandle(this Window window) + { + return new WindowInteropHelper(window).Handle; + } + } +} diff --git a/Library/Xna/FrameRateCounter.cs b/Library/Xna/FrameRateCounter.cs index aae716f..f288dac 100644 --- a/Library/Xna/FrameRateCounter.cs +++ b/Library/Xna/FrameRateCounter.cs @@ -5,13 +5,11 @@ namespace Jellyfish.Library { - public class FrameRateCounter : DrawableGameComponent + public sealed class FrameRateCounter : DrawableGameComponent { public FrameRateCounter(GameBase game) : base(game) { - _frameRateBuilder = new StringBuilder(); // cache builder; avoids garbage - FontColor = Color.White; FontName = "Default"; @@ -68,6 +66,6 @@ public override void Update(GameTime gameTime) private long _elapsedTime; private int _frameCount; private int _frameRate; - private StringBuilder _frameRateBuilder; + private StringBuilder _frameRateBuilder = new StringBuilder(); // cache builder; avoids garbage } } diff --git a/Library/Xna/GameBase.cs b/Library/Xna/GameBase.cs index 068d760..7a57708 100644 --- a/Library/Xna/GameBase.cs +++ b/Library/Xna/GameBase.cs @@ -7,11 +7,6 @@ namespace Jellyfish.Library { public class GameBase : Game { - public GameBase() : - this(null) - { - } - public GameBase(string name) { Name = name; diff --git a/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj b/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj index a0752ef..28dba83 100644 --- a/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj +++ b/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj @@ -62,9 +62,6 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs - GlobalSuppressions.cs diff --git a/Library/Xna/Jellyfish.Library.Xna.Zune.csproj b/Library/Xna/Jellyfish.Library.Xna.Zune.csproj index 490ce35..8dc5d3e 100644 --- a/Library/Xna/Jellyfish.Library.Xna.Zune.csproj +++ b/Library/Xna/Jellyfish.Library.Xna.Zune.csproj @@ -60,9 +60,6 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs - GlobalSuppressions.cs diff --git a/Library/Xna/Jellyfish.Library.Xna.csproj b/Library/Xna/Jellyfish.Library.Xna.csproj index b2cc6e8..f7acfb6 100644 --- a/Library/Xna/Jellyfish.Library.Xna.csproj +++ b/Library/Xna/Jellyfish.Library.Xna.csproj @@ -84,8 +84,14 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs + + DirectSound.cs + + + DirectSoundInterop.cs + + + GCHandleHelpers.cs GeneralSecurity.cs @@ -99,6 +105,9 @@ Lazy.cs + + MarshalHelpers.cs + MathHelpers.cs diff --git a/Virtu/Cpu.cs b/Virtu/Cpu.cs index 29f0b36..e834c97 100644 --- a/Virtu/Cpu.cs +++ b/Virtu/Cpu.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Threading; -using Jellyfish.Virtu.Properties; namespace Jellyfish.Virtu { @@ -187,6 +186,7 @@ public int Execute() Opcode = _memory.Read(RPC); RPC = (RPC + 1) & 0xFFFF; _executeOpcode[Opcode](); + Cycles += CC; // System.Diagnostics.Debug.WriteLine(" " + ToString()); @@ -3243,6 +3243,7 @@ private void UpdateSettings() public int EA { get; private set; } public int CC { get; private set; } public int Opcode { get; private set; } + public long Cycles { get; private set; } private Action _updateEvent; diff --git a/Virtu/CpuData.cs b/Virtu/CpuData.cs index e227cb4..ef70640 100644 --- a/Virtu/CpuData.cs +++ b/Virtu/CpuData.cs @@ -7,7 +7,7 @@ public partial class Cpu private const int CyclesPerUpdate = 17030; private const int CyclesPerVSync = 17030; private const int CyclesPerSecond = 1022730; - private static readonly long TicksPerVSync = TimeSpan.FromSeconds((double)CyclesPerVSync / (double)CyclesPerSecond).Ticks; + private static readonly long TicksPerVSync = TimeSpan.FromSeconds((double)CyclesPerVSync / CyclesPerSecond).Ticks; private const int OpcodeCount = 256; diff --git a/Virtu/CustomDictionary.xml b/Virtu/CustomDictionary.xml index 1baed9f..928be94 100644 --- a/Virtu/CustomDictionary.xml +++ b/Virtu/CustomDictionary.xml @@ -11,6 +11,7 @@ Annunciator Dsk Opcode + Unpause Virtu Xna x diff --git a/Virtu/DiskDsk.cs b/Virtu/DiskDsk.cs index 27b157e..7ced3b0 100644 --- a/Virtu/DiskDsk.cs +++ b/Virtu/DiskDsk.cs @@ -266,25 +266,25 @@ private void WriteDataNibbles(int sectorOffset) private static readonly byte[] NibbleToByte = new byte[] { // padding for offset (not used) - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, // nibble translate table - 0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06, - 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, - 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E, - 0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, - 0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F + 0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E, + 0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, + 0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F }; } } diff --git a/Virtu/DiskII.cs b/Virtu/DiskII.cs index 9d3fcb3..c6c4a31 100644 --- a/Virtu/DiskII.cs +++ b/Virtu/DiskII.cs @@ -1,25 +1,19 @@ using System.Diagnostics.CodeAnalysis; -using Jellyfish.Virtu.Services; using Jellyfish.Virtu.Settings; namespace Jellyfish.Virtu { public sealed class DiskII : MachineComponent { - public DiskII(Machine machine) : + public DiskII(Machine machine) : base(machine) { } public override void Initialize() { - _storageService = Machine.Services.GetService(); - +#if WINDOWS DiskIISettings settings = Machine.Settings.DiskII; - if (settings.Disk1.Name.Length == 0) - { - settings.Disk1.Name = _storageService.GetDiskFile(); - } if (settings.Disk1.Name.Length > 0) { _drives[0].InsertDisk(settings.Disk1.Name, settings.Disk1.IsWriteProtected); @@ -28,6 +22,7 @@ public override void Initialize() { _drives[1].InsertDisk(settings.Disk2.Name, settings.Disk2.IsWriteProtected); } +#endif } public override void Reset() @@ -42,10 +37,6 @@ public override void Reset() public override void Uninitialize() { Flush(); - - DiskIISettings settings = Machine.Settings.DiskII; // TODO remove; reset filename to prompt on next start - settings.Disk1.Name = string.Empty; - settings.Disk2.Name = string.Empty; } [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] @@ -227,13 +218,14 @@ private void SetPhase(int address) } } + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public Drive525[] Drives { get { return _drives; } } + private const int Phase0On = 1 << 0; private const int Phase1On = 1 << 1; private const int Phase2On = 1 << 2; private const int Phase3On = 1 << 3; - private StorageService _storageService; - private Drive525[] _drives = new Drive525[] { new Drive525(), new Drive525() }; private int _latch; private int _phaseStates; diff --git a/Virtu/Drive525.cs b/Virtu/Drive525.cs index d07142a..e036ec9 100644 --- a/Virtu/Drive525.cs +++ b/Virtu/Drive525.cs @@ -1,9 +1,10 @@ using System; +using System.IO; using Jellyfish.Library; namespace Jellyfish.Virtu { - public class Drive525 + public sealed class Drive525 { public Drive525() { @@ -14,13 +15,20 @@ public Drive525() } public void InsertDisk(string fileName, bool isWriteProtected) + { + using (FileStream stream = File.OpenRead(fileName)) + { + InsertDisk(fileName, stream, isWriteProtected); + } + } + + public void InsertDisk(string name, Stream stream, bool isWriteProtected) { FlushTrack(); // TODO handle null param/empty string for eject, or add Eject() - byte[] fileData = FileHelpers.ReadAllBytes(fileName); - _disk = Disk525.CreateDisk(fileName, fileData, isWriteProtected); + _disk = Disk525.CreateDisk(name, stream.ReadAllBytes(), isWriteProtected); _trackLoaded = false; } diff --git a/Virtu/GamePort.cs b/Virtu/GamePort.cs index 5712b63..1b009cc 100644 --- a/Virtu/GamePort.cs +++ b/Virtu/GamePort.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; using Jellyfish.Library; -using Jellyfish.Virtu.Properties; using Jellyfish.Virtu.Services; using Jellyfish.Virtu.Settings; diff --git a/Virtu/Keyboard.cs b/Virtu/Keyboard.cs index 8a697f2..51be1e9 100644 --- a/Virtu/Keyboard.cs +++ b/Virtu/Keyboard.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using Jellyfish.Virtu.Properties; using Jellyfish.Virtu.Services; using Jellyfish.Virtu.Settings; diff --git a/Virtu/Machine.cs b/Virtu/Machine.cs index 6327050..5a99f7f 100644 --- a/Virtu/Machine.cs +++ b/Virtu/Machine.cs @@ -1,4 +1,5 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.ObjectModel; using System.Threading; using Jellyfish.Library; using Jellyfish.Virtu.Services; @@ -6,11 +7,12 @@ namespace Jellyfish.Virtu { - public class Machine + public enum MachineState { Stopped = 0, Starting, Running, Pausing, Paused, Stopping } + + public sealed class Machine : IDisposable { public Machine() { - Thread = new Thread(Run) { Name = "Machine" }; Events = new MachineEvents(); Services = new MachineServices(); Settings = new MachineSettings(); @@ -23,8 +25,15 @@ public Machine() Cassette = new Cassette(this); Speaker = new Speaker(this); Video = new Video(this); - Components = new Collection { Cpu, Memory, DiskII, Keyboard, GamePort, Cassette, Speaker, Video }; + + Thread = new Thread(Run) { Name = "Machine" }; + } + + public void Dispose() + { + _pausedEvent.Close(); + _unpauseEvent.Close(); } public void Reset() @@ -36,35 +45,64 @@ public void Start() { _storageService = Services.GetService(); _storageService.Load(MachineSettings.FileName, stream => Settings.Deserialize(stream)); + + State = MachineState.Starting; Thread.Start(); } + public void Pause() + { + State = MachineState.Pausing; + _pausedEvent.WaitOne(); + State = MachineState.Paused; + } + + public void Unpause() + { + State = MachineState.Running; + _unpauseEvent.Set(); + } + public void Stop() { - _stopPending = true; + State = MachineState.Stopping; + _unpauseEvent.Set(); Thread.Join(); + State = MachineState.Stopped; + _storageService.Save(MachineSettings.FileName, stream => Settings.Serialize(stream)); } - private void Run() + private void Run() // machine thread { Components.ForEach(component => component.Initialize()); Reset(); + State = MachineState.Running; do { - Events.RaiseEvents(Cpu.Execute()); + do + { + Events.RaiseEvents(Cpu.Execute()); + } + while (State == MachineState.Running); + + if (State == MachineState.Pausing) + { + _pausedEvent.Set(); + _unpauseEvent.WaitOne(); + } } - while (!_stopPending); + while (State != MachineState.Stopping); Components.ForEach(component => component.Uninitialize()); } - public Thread Thread { get; private set; } public MachineEvents Events { get; private set; } public MachineServices Services { get; private set; } public MachineSettings Settings { get; private set; } - public Collection Components { get; private set; } + public MachineState State { get; private set; } + public Cpu Cpu { get; private set; } public Memory Memory { get; private set; } public DiskII DiskII { get; private set; } @@ -73,8 +111,13 @@ private void Run() public Cassette Cassette { get; private set; } public Speaker Speaker { get; private set; } public Video Video { get; private set; } + public Collection Components { get; private set; } + + public Thread Thread { get; private set; } + + private AutoResetEvent _pausedEvent = new AutoResetEvent(false); + private AutoResetEvent _unpauseEvent = new AutoResetEvent(false); private StorageService _storageService; - private bool _stopPending; } } diff --git a/Virtu/MachineEvents.cs b/Virtu/MachineEvents.cs index 1a6d283..388d9c7 100644 --- a/Virtu/MachineEvents.cs +++ b/Virtu/MachineEvents.cs @@ -5,7 +5,7 @@ namespace Jellyfish.Virtu { - public class MachineEvent + public sealed class MachineEvent { public MachineEvent(int delta, Action action) { @@ -22,7 +22,7 @@ public override string ToString() public Action Action { get; set; } } - public class MachineEvents + public sealed class MachineEvents { public void AddEvent(int delta, Action action) { @@ -75,7 +75,7 @@ public int FindEvent(Action action) } } - return delta; + return 0; } [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] @@ -86,17 +86,23 @@ public void RaiseEvents(int delta) while (node.Value.Delta <= 0) { - delta = node.Value.Delta; node.Value.Action(); - - _used.Remove(node); - _free.AddFirst(node); // cache node; avoids garbage - + RemoveEvent(node); node = _used.First; - node.Value.Delta += delta; } } + private void RemoveEvent(LinkedListNode node) + { + if (node.Next != null) + { + node.Next.Value.Delta += node.Value.Delta; + } + + _used.Remove(node); + _free.AddFirst(node); // cache node; avoids garbage + } + private LinkedList _used = new LinkedList(); private LinkedList _free = new LinkedList(); } diff --git a/Virtu/MachineSettings.cs b/Virtu/MachineSettings.cs index 351d228..100d2ce 100644 --- a/Virtu/MachineSettings.cs +++ b/Virtu/MachineSettings.cs @@ -6,7 +6,7 @@ namespace Jellyfish.Virtu.Settings { - public class MachineSettings + public sealed class MachineSettings { public MachineSettings() { @@ -21,8 +21,8 @@ public MachineSettings() UseGamePort = false, Key = new KeySettings { - Joystick0 = new JoystickSettings { UpLeft = 0, Up = 'E', UpRight = 0, Left = 'S', Right = 'F', DownLeft = 0, Down = 'D', DownRight = 0 }, - Joystick1 = new JoystickSettings { UpLeft = 0, Up = 'I', UpRight = 0, Left = 'J', Right = 'L', DownLeft = 0, Down = 'K', DownRight = 0 }, + Joystick0 = new JoystickSettings { UpLeft = 0, Up = 'I', UpRight = 0, Left = 'J', Right = 'L', DownLeft = 0, Down = 'K', DownRight = 0 }, + Joystick1 = new JoystickSettings { UpLeft = 0, Up = 'E', UpRight = 0, Left = 'S', Right = 'F', DownLeft = 0, Down = 'D', DownRight = 0 }, Button0 = 0, Button1 = 0, Button2 = 0 } }; @@ -305,25 +305,25 @@ public void Serialize(Stream stream) public const string Namespace = "http://schemas.jellyfish.co.nz/virtu/settings"; } - public class CpuSettings + public sealed class CpuSettings { public bool Is65C02 { get; set; } public bool IsThrottled { get; set; } } - public class DiskSettings + public sealed class DiskSettings { public string Name { get; set; } public bool IsWriteProtected { get; set; } }; - public class DiskIISettings + public sealed class DiskIISettings { public DiskSettings Disk1 { get; set; } public DiskSettings Disk2 { get; set; } }; - public class JoystickSettings + public sealed class JoystickSettings { public int UpLeft { get; set; } public int Up { get; set; } @@ -335,7 +335,7 @@ public class JoystickSettings public int DownRight { get; set; } }; - public class KeySettings + public sealed class KeySettings { public JoystickSettings Joystick0 { get; set; } public JoystickSettings Joystick1 { get; set; } @@ -344,19 +344,19 @@ public class KeySettings public int Button2 { get; set; } }; - public class KeyboardSettings + public sealed class KeyboardSettings { public bool UseGamePort { get; set; } public KeySettings Key { get; set; } } - public class GamePortSettings + public sealed class GamePortSettings { public bool UseKeyboard { get; set; } public KeySettings Key { get; set; } } - public class ColorSettings + public sealed class ColorSettings { public uint Black { get; set; } public uint DarkBlue { get; set; } @@ -377,7 +377,7 @@ public class ColorSettings public uint Monochrome { get; set; } } - public class VideoSettings + public sealed class VideoSettings { public bool IsFullScreen { get; set; } public bool IsMonochrome { get; set; } diff --git a/Virtu/Properties/SR.Designer.cs b/Virtu/Properties/SR.Designer.cs index c1702e6..13a2298 100644 --- a/Virtu/Properties/SR.Designer.cs +++ b/Virtu/Properties/SR.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:2.0.50727.3074 +// Runtime Version:2.0.50727.4016 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/Virtu/Roms/AppleIIe.rom b/Virtu/Roms/AppleIIe.rom new file mode 100644 index 0000000..65bb648 Binary files /dev/null and b/Virtu/Roms/AppleIIe.rom differ diff --git a/Virtu/Roms/DiskII.rom b/Virtu/Roms/DiskII.rom new file mode 100644 index 0000000..56698b0 Binary files /dev/null and b/Virtu/Roms/DiskII.rom differ diff --git a/Virtu/Services/AudioService.cs b/Virtu/Services/AudioService.cs index 4625237..9fdca3e 100644 --- a/Virtu/Services/AudioService.cs +++ b/Virtu/Services/AudioService.cs @@ -1,9 +1,86 @@ using System; +using System.Linq; namespace Jellyfish.Virtu.Services { - public class AudioService + public class AudioService : MachineService { - public virtual void Update() { } + public AudioService(Machine machine) : + base(machine) + { + } + + public void ToggleOutput() // machine thread + { + lock (_lock) + { + long cycles = Machine.Cpu.Cycles; + int toggleDelta = (int)(cycles - _toggleCycles); + _toggleCycles = cycles; + + _deltaBuffer[_writeIndex] = toggleDelta; + _writeIndex = (_writeIndex + 1) % DeltaBufferSize; + } + } + + protected void Update(int bufferSize, Action updateBuffer) // audio thread + { + lock (_lock) + { + long cycles = Machine.Cpu.Cycles; + int updateDelta = (int)(cycles - _updateCycles); + _updateCycles = _toggleCycles = cycles; // reset audio frame + + if (updateDelta > 0) + { + double bytesPerCycle = (double)bufferSize / (Machine.Settings.Cpu.IsThrottled ? CyclesPerSample : updateDelta); + + while (_readIndex != _writeIndex) + { + int deltaSize = (int)(_deltaBuffer[_readIndex] * bytesPerCycle); + if (deltaSize > bufferSize) + { + _deltaBuffer[_readIndex] -= (int)((double)bufferSize / bytesPerCycle); + break; + } + + updateBuffer(_isOutputHigh ? SampleHigh : SampleZero, deltaSize); + _isOutputHigh ^= true; + + bufferSize -= deltaSize; + _readIndex = (_readIndex + 1) % DeltaBufferSize; + } + + updateBuffer(_isOutputHigh ? SampleHigh : SampleZero, bufferSize); + } + else + { + updateBuffer(SampleZero, bufferSize); + } + } + } + + public const int SampleRate = 44100; // hz + public const int SampleChannels = 1; + public const int SampleBits = 8; + public const int SampleSize = (int)(SampleRate * Latency / 1000f) * SampleChannels * SampleBits / 8; + + private const int CyclesPerSecond = 1022730; + private const int CyclesPerSample = (int)(CyclesPerSecond * Latency / 1000f); + + private const int Latency = 40; // ms + + private static readonly byte[] SampleHigh = Enumerable.Repeat((byte)0xFF, SampleSize).ToArray(); + private static readonly byte[] SampleZero = new byte[SampleSize]; + + private const int DeltaBufferSize = 8192; + + private int[] _deltaBuffer = new int[DeltaBufferSize]; + private uint _readIndex; + private uint _writeIndex; + private bool _isOutputHigh; + private long _toggleCycles; + private long _updateCycles; + private object _lock = new object(); } } diff --git a/Virtu/Services/GamePortService.cs b/Virtu/Services/GamePortService.cs index 189165f..08b34f4 100644 --- a/Virtu/Services/GamePortService.cs +++ b/Virtu/Services/GamePortService.cs @@ -48,9 +48,10 @@ public override string ToString() private bool _isDown; } - public class GamePortService + public class GamePortService : MachineService { - public GamePortService() + public GamePortService(Machine machine) : + base(machine) { Paddle0 = Paddle1 = Paddle2 = Paddle3 = 255; // not connected } diff --git a/Virtu/Services/KeyboardService.cs b/Virtu/Services/KeyboardService.cs index a39e3b8..f124e56 100644 --- a/Virtu/Services/KeyboardService.cs +++ b/Virtu/Services/KeyboardService.cs @@ -4,7 +4,7 @@ namespace Jellyfish.Virtu.Services { - public class AsciiKeyEventArgs : EventArgs + public sealed class AsciiKeyEventArgs : EventArgs { private AsciiKeyEventArgs() { @@ -22,8 +22,13 @@ public static AsciiKeyEventArgs Create(int asciiKey) private static readonly AsciiKeyEventArgs _instance = new AsciiKeyEventArgs(); } - public abstract class KeyboardService + public abstract class KeyboardService : MachineService { + protected KeyboardService(Machine machine) : + base(machine) + { + } + public abstract bool IsKeyDown(int key); [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] @@ -42,7 +47,7 @@ public void WaitForKeyUp() { while (IsAnyKeyDown) { - Thread.Sleep(100); + Thread.Sleep(10); } } @@ -50,7 +55,7 @@ public void WaitForResetKeyUp() { while (IsResetKeyDown) { - Thread.Sleep(100); + Thread.Sleep(10); } } diff --git a/Virtu/Services/MachineService.cs b/Virtu/Services/MachineService.cs new file mode 100644 index 0000000..fe74e02 --- /dev/null +++ b/Virtu/Services/MachineService.cs @@ -0,0 +1,29 @@ +using System; + +namespace Jellyfish.Virtu.Services +{ + public abstract class MachineService : IDisposable + { + protected MachineService(Machine machine) + { + Machine = machine; + } + + ~MachineService() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + + protected Machine Machine { get; private set; } + } +} diff --git a/Virtu/Services/MachineServices.cs b/Virtu/Services/MachineServices.cs index b243205..ac8bd9c 100644 --- a/Virtu/Services/MachineServices.cs +++ b/Virtu/Services/MachineServices.cs @@ -5,9 +5,9 @@ namespace Jellyfish.Virtu.Services { - public class MachineServices : IServiceProvider + public sealed class MachineServices : IServiceProvider { - public void AddService(Type serviceType, object serviceProvider) + public void AddService(Type serviceType, MachineService serviceProvider) { if (_serviceProviders.ContainsKey(serviceType)) { @@ -37,6 +37,6 @@ public void RemoveService(Type serviceType) _serviceProviders.Remove(serviceType); } - private Dictionary _serviceProviders = new Dictionary(); + private Dictionary _serviceProviders = new Dictionary(); } } diff --git a/Virtu/Services/StorageService.cs b/Virtu/Services/StorageService.cs index cc4d0af..667ae53 100644 --- a/Virtu/Services/StorageService.cs +++ b/Virtu/Services/StorageService.cs @@ -1,13 +1,15 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.IO; namespace Jellyfish.Virtu.Services { - public abstract class StorageService + public abstract class StorageService : MachineService { - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - public abstract string GetDiskFile(); + protected StorageService(Machine machine) : + base(machine) + { + } + public abstract void Load(string path, Action reader); public abstract void Save(string path, Action writer); } diff --git a/Virtu/Services/VideoService.cs b/Virtu/Services/VideoService.cs index 06b9bcd..3e47ac1 100644 --- a/Virtu/Services/VideoService.cs +++ b/Virtu/Services/VideoService.cs @@ -1,9 +1,12 @@ -using System; - -namespace Jellyfish.Virtu.Services +namespace Jellyfish.Virtu.Services { - public abstract class VideoService + public abstract class VideoService : MachineService { + protected VideoService(Machine machine) : + base(machine) + { + } + public abstract void SetPixel(int x, int y, uint color); public abstract void Update(); diff --git a/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj b/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj index 0717b64..f3e2a6c 100644 --- a/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj +++ b/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj @@ -62,6 +62,7 @@ + @@ -130,6 +131,9 @@ Services\KeyboardService.cs + + Services\MachineService.cs + Services\MachineServices.cs @@ -155,6 +159,7 @@ MainPage.xaml + diff --git a/Virtu/Silverlight/MainApp.xaml.cs b/Virtu/Silverlight/MainApp.xaml.cs index 91b7615..1cd9c9d 100644 --- a/Virtu/Silverlight/MainApp.xaml.cs +++ b/Virtu/Silverlight/MainApp.xaml.cs @@ -2,7 +2,7 @@ namespace Jellyfish.Virtu { - public partial class MainApp : ApplicationBase + public sealed partial class MainApp : ApplicationBase { public MainApp() : base("Virtu") diff --git a/Virtu/Silverlight/MainPage.xaml b/Virtu/Silverlight/MainPage.xaml index d9c33d8..2aa6080 100644 --- a/Virtu/Silverlight/MainPage.xaml +++ b/Virtu/Silverlight/MainPage.xaml @@ -1,11 +1,18 @@  - - - - - + + +