From 5a56d8a5d0d448a91d3ced8c6c4067283f830ff1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Jun 2018 18:53:56 -0400 Subject: [PATCH 1/8] Exposes a list of machine LEDs to Swift. Also gets explicit about nullability on the Objective-C side. --- .../Documents/MachineDocument.swift | 42 ++++++++++++++++++- .../Mac/Clock Signal/Machine/CSMachine.h | 27 ++++++------ .../Mac/Clock Signal/Machine/CSMachine.mm | 41 ++++++++++++++++++ 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 61b403bd0..6ae182abc 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -102,7 +102,7 @@ class MachineDocument: } } - func machineSpeakerDidChangeInputClock(_ machine: CSMachine!) { + func machineSpeakerDidChangeInputClock(_ machine: CSMachine) { setupAudioQueueClockRate() } @@ -147,6 +147,7 @@ class MachineDocument: self.machine = machine self.optionsPanelNibName = analysis.optionsPanelNibName setupMachineOutput() + setupActivityDisplay() } } @@ -290,4 +291,43 @@ class MachineDocument: } return super.validateUserInterfaceItem(item) } + + // MARK: Activity display. + func setupActivityDisplay() { + return + + if machine.leds.count > 0 { + let panel = NSPanel() + panel.title = "Activity" + panel.styleMask = .hudWindow + panel.setIsVisible(true) + + for name in machine.leds { + let button = NSButton() + button.title = name + button.setButtonType(.radio) + button.translatesAutoresizingMaskIntoConstraints = false + button.isEnabled = false +// button.color + panel.contentView?.addSubview(button) + + let views = ["button": button] + let horizontalConstraints = + NSLayoutConstraint.constraints( + withVisualFormat: "H:|-[button]-|", + options: NSLayoutConstraint.FormatOptions(rawValue: 0), + metrics: nil, + views: views) + let verticalConstraints = + NSLayoutConstraint.constraints( + withVisualFormat: "V:|-[button]-|", + options: NSLayoutConstraint.FormatOptions(rawValue: 0), + metrics: nil, + views: views) + + panel.contentView?.addConstraints(horizontalConstraints) + panel.contentView?.addConstraints(verticalConstraints) + } + } + } } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index 4b0ceddb8..670a446ce 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -15,7 +15,7 @@ @class CSMachine; @protocol CSMachineDelegate -- (void)machineSpeakerDidChangeInputClock:(CSMachine *)machine; +- (void)machineSpeakerDidChangeInputClock:(nonnull CSMachine *)machine; @end typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { @@ -35,32 +35,32 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { @interface CSMachine : NSObject -- (instancetype)init NS_UNAVAILABLE; +- (nonnull instancetype)init NS_UNAVAILABLE; /*! Initialises an instance of CSMachine. @param result The CSStaticAnalyser result that describes the machine needed. */ -- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER; - (void)runForInterval:(NSTimeInterval)interval; - (float)idealSamplingRateFromRange:(NSRange)range; - (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize; -- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio; +- (void)setView:(nullable CSOpenGLView *)view aspectRatio:(float)aspectRatio; - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; -- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed; +- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed; - (void)clearAllKeys; -@property (nonatomic, strong) CSAudioQueue *audioQueue; -@property (nonatomic, readonly) CSOpenGLView *view; -@property (nonatomic, weak) id delegate; +@property (nonatomic, strong, nullable) CSAudioQueue *audioQueue; +@property (nonatomic, readonly, nonnull) CSOpenGLView *view; +@property (nonatomic, weak, nullable) id delegate; -@property (nonatomic, readonly) NSString *userDefaultsPrefix; +@property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix; -- (void)paste:(NSString *)string; +- (void)paste:(nonnull NSString *)string; @property (nonatomic, assign) BOOL useFastLoadingHack; @property (nonatomic, assign) CSMachineVideoSignal videoSignal; @@ -73,8 +73,11 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { @property (nonatomic, readonly) BOOL hasJoystick; @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; +// LED list. +@property (nonatomic, readonly, nonnull) NSArray *leds; + // Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type. -@property (nonatomic, readonly) CSAtari2600 *atari2600; -@property (nonatomic, readonly) CSZX8081 *zx8081; +@property (nonatomic, readonly, nullable) CSAtari2600 *atari2600; +@property (nonatomic, readonly, nullable) CSZX8081 *zx8081; @end diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index a0ca43fb1..910773d5a 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -18,6 +18,7 @@ #include "MachineForTarget.hpp" #include "StandardOptions.hpp" #include "Typer.hpp" +#include "../../../../Activity/Observer.hpp" #import "CSStaticAnalyser+TargetVector.h" #import "NSBundle+DataResource.h" @@ -28,6 +29,7 @@ @interface CSMachine() - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker; +- (void)addLED:(NSString *)led; @end struct LockProtectedDelegate { @@ -50,14 +52,36 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP } }; +struct ActivityObserver: public Activity::Observer { + void register_led(const std::string &name) override { + [machine addLED:[NSString stringWithUTF8String:name.c_str()]]; + } + + void register_drive(const std::string &name) override { + } + + void set_led_status(const std::string &name, bool lit) override { + } + + void announce_drive_event(const std::string &name, DriveEvent event) override { + } + + void set_drive_motor_status(const std::string &name, bool is_on) override { + } + + __unsafe_unretained CSMachine *machine; +}; + @implementation CSMachine { SpeakerDelegate _speakerDelegate; + ActivityObserver _activityObserver; NSLock *_delegateMachineAccessLock; CSStaticAnalyser *_analyser; std::unique_ptr _machine; std::bitset<65536> _depressedKeys; + NSMutableArray *_leds; } - (instancetype)initWithAnalyser:(CSStaticAnalyser *)result { @@ -71,6 +95,13 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP _inputMode = _machine->keyboard_machine() ? CSMachineKeyboardInputModeKeyboard : CSMachineKeyboardInputModeJoystick; + _leds = [[NSMutableArray alloc] init]; + Activity::Source *const activity_source = _machine->activity_source(); + if(activity_source) { + _activityObserver.machine = self; + activity_source->set_activity_observer(&_activityObserver); + } + _delegateMachineAccessLock = [[NSLock alloc] init]; _speakerDelegate.machine = self; @@ -408,4 +439,14 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP return !!_machine->keyboard_machine(); } +#pragma mark - Activity observation + +- (void)addLED:(NSString *)led { + [_leds addObject:led]; +} + +- (NSArray *)leds { + return _leds; +} + @end From 292e02702a8e3b97b45084b466818c5c47b22080 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Jun 2018 22:52:17 -0400 Subject: [PATCH 2/8] Progresses very slightly to being able to show up to four activity indicator names. Blinking to come. --- .../Clock Signal.xcodeproj/project.pbxproj | 12 ++ .../Mac/Clock Signal/Base.lproj/Activity.xib | 117 ++++++++++++++++++ .../Mac/Clock Signal/Base.lproj/MainMenu.xib | 10 +- .../Documents/MachineDocument.swift | 71 ++++++----- 4 files changed, 179 insertions(+), 31 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 863eeedcb..2b21d153e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -115,6 +115,7 @@ 4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0783591FC11D10001D12BB /* Configurable.cpp */; }; 4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; }; 4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; }; + 4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B08A56720D72BEF0016CE5A /* Activity.xib */; }; 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; 4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; @@ -705,6 +706,7 @@ 4B08A2761EE39306008B7065 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = ""; }; 4B08A2771EE39306008B7065 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = ""; }; 4B08A2791EE3957B008B7065 /* TestMachine+ForSubclassEyesOnly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestMachine+ForSubclassEyesOnly.h"; sourceTree = ""; }; + 4B08A56820D72BEF0016CE5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Activity.xib"; sourceTree = SOURCE_ROOT; }; 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = ""; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; @@ -1988,6 +1990,7 @@ 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */, 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */, + 4B08A56720D72BEF0016CE5A /* Activity.xib */, 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */, 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, @@ -3284,6 +3287,7 @@ 4BA141BD2072E8A500A31EC9 /* MachinePicker.xib in Resources */, 4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, 4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, + 4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */, 4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */, 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, @@ -3984,6 +3988,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 4B08A56720D72BEF0016CE5A /* Activity.xib */ = { + isa = PBXVariantGroup; + children = ( + 4B08A56820D72BEF0016CE5A /* Base */, + ); + name = Activity.xib; + sourceTree = ""; + }; 4B1497961EE4B97F00CE2596 /* ZX8081Options.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib new file mode 100644 index 000000000..2f3e6e302 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib b/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib index ccd7fca32..ff476331c 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib @@ -146,10 +146,16 @@ - + - + + + + + + + diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 6ae182abc..b3b6330c6 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -40,6 +40,11 @@ class MachineDocument: optionsPanel?.setIsVisible(true) } + @IBOutlet var activityPanel: NSPanel! + @IBAction func showActivity(_ sender: AnyObject!) { + activityPanel.setIsVisible(true) + } + fileprivate var audioQueue: CSAudioQueue! = nil fileprivate var bestEffortUpdater: CSBestEffortUpdater? @@ -119,6 +124,9 @@ class MachineDocument: } override func close() { + activityPanel?.setIsVisible(false) + activityPanel = nil + optionsPanel?.setIsVisible(false) optionsPanel = nil @@ -286,6 +294,9 @@ class MachineDocument: menuItem.state = machine.inputMode == .joystick ? .on : .off return true + case #selector(self.showActivity(_:)): + return self.activityPanel != nil + default: break } } @@ -293,40 +304,42 @@ class MachineDocument: } // MARK: Activity display. + fileprivate var activityLevelIndicators: [String: NSLevelIndicator] = [:] func setupActivityDisplay() { - return + var leds = machine.leds + if leds.count > 0 { + Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil) + showActivity(nil) - if machine.leds.count > 0 { - let panel = NSPanel() - panel.title = "Activity" - panel.styleMask = .hudWindow - panel.setIsVisible(true) + // Inspect the activity panel for indicators. + var activityIndicators: [NSLevelIndicator] = [] + var textFields: [NSTextField] = [] + if let contentView = self.activityPanel.contentView { + for view in contentView.subviews { + if let levelIndicator = view as? NSLevelIndicator { + activityIndicators.append(levelIndicator) + } - for name in machine.leds { - let button = NSButton() - button.title = name - button.setButtonType(.radio) - button.translatesAutoresizingMaskIntoConstraints = false - button.isEnabled = false -// button.color - panel.contentView?.addSubview(button) + if let textField = view as? NSTextField { + textFields.append(textField) + } + } + } - let views = ["button": button] - let horizontalConstraints = - NSLayoutConstraint.constraints( - withVisualFormat: "H:|-[button]-|", - options: NSLayoutConstraint.FormatOptions(rawValue: 0), - metrics: nil, - views: views) - let verticalConstraints = - NSLayoutConstraint.constraints( - withVisualFormat: "V:|-[button]-|", - options: NSLayoutConstraint.FormatOptions(rawValue: 0), - metrics: nil, - views: views) + // If there are fewer level indicators than LEDs, trim that list. + if activityIndicators.count < leds.count { + leds.removeSubrange(activityIndicators.count ..< leds.count) + } - panel.contentView?.addConstraints(horizontalConstraints) - panel.contentView?.addConstraints(verticalConstraints) + // Remove unused views. + for c in leds.count ..< activityIndicators.count { + textFields[c].removeFromSuperview() + activityIndicators[c].removeFromSuperview() + } + + // Apply labels. + for c in 0 ..< leds.count { + textFields[c].stringValue = leds[c] } } } From 17702bfb89891927d1c10e5f28a1070ac50cbec6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jun 2018 21:22:51 -0400 Subject: [PATCH 3/8] Causes GUI LEDs to reflect their underlying activity. --- .../Mac/Clock Signal/Base.lproj/Activity.xib | 8 ++-- .../Documents/MachineDocument.swift | 42 ++++++++++++++++++- .../Mac/Clock Signal/Machine/CSMachine.h | 2 + .../Mac/Clock Signal/Machine/CSMachine.mm | 2 + 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib index 2f3e6e302..3385e40c0 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Activity.xib @@ -28,7 +28,7 @@ - + @@ -43,7 +43,7 @@ - + @@ -58,7 +58,7 @@ - + @@ -73,7 +73,7 @@ - + diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index b3b6330c6..b04ae9304 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -304,7 +304,15 @@ class MachineDocument: } // MARK: Activity display. - fileprivate var activityLevelIndicators: [String: NSLevelIndicator] = [:] + class LED { + let levelIndicator: NSLevelIndicator + init(levelIndicator: NSLevelIndicator) { + self.levelIndicator = levelIndicator + } + var isLit = false + var isBlinking = false + } + fileprivate var leds: [String: LED] = [:] func setupActivityDisplay() { var leds = machine.leds if leds.count > 0 { @@ -337,9 +345,39 @@ class MachineDocument: activityIndicators[c].removeFromSuperview() } - // Apply labels. + // Apply labels and create leds entries. for c in 0 ..< leds.count { textFields[c].stringValue = leds[c] + self.leds[leds[c]] = LED(levelIndicator: activityIndicators[c]) + } + } + } + + func machine(_ machine: CSMachine, ledShouldBlink ledName: String) { + // If there is such an LED, switch it off for 0.03 of a second; if it's meant + // to be off at the end of that, leave it off. Don't allow the blinks to + // pile up — allow there to be only one in flight at a time. + if let led = leds[ledName] { + DispatchQueue.main.async { + if !led.isBlinking { + led.levelIndicator.floatValue = 0.0 + led.isBlinking = true + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) { + led.levelIndicator.floatValue = led.isLit ? 1.0 : 0.0 + led.isBlinking = false + } + } + } + } + } + + func machine(_ machine: CSMachine, led ledName: String, didChangeToLit isLit: Bool) { + // If there is such an LED, switch it appropriately. + if let led = leds[ledName] { + DispatchQueue.main.async { + led.levelIndicator.floatValue = isLit ? 1.0 : 0.0 + led.isLit = isLit } } } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index 670a446ce..0f6bb7636 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -16,6 +16,8 @@ @class CSMachine; @protocol CSMachineDelegate - (void)machineSpeakerDidChangeInputClock:(nonnull CSMachine *)machine; +- (void)machine:(nonnull CSMachine *)machine led:(nonnull NSString *)led didChangeToLit:(BOOL)isLit; +- (void)machine:(nonnull CSMachine *)machine ledShouldBlink:(nonnull NSString *)led; @end typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 910773d5a..ee3335573 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -61,9 +61,11 @@ struct ActivityObserver: public Activity::Observer { } void set_led_status(const std::string &name, bool lit) override { + [machine.delegate machine:machine led:[NSString stringWithUTF8String:name.c_str()] didChangeToLit:lit]; } void announce_drive_event(const std::string &name, DriveEvent event) override { + [machine.delegate machine:machine ledShouldBlink:[NSString stringWithUTF8String:name.c_str()]]; } void set_drive_motor_status(const std::string &name, bool is_on) override { From 6ed3a49fe16e3eba000963a0fecdc2d891a634b6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jun 2018 21:35:22 -0400 Subject: [PATCH 4/8] Made failed attempt to apply height constraint. --- .../Mac/Clock Signal/Documents/MachineDocument.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index b04ae9304..90b92dd71 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -319,6 +319,18 @@ class MachineDocument: Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil) showActivity(nil) + // Add a constraints to minimise window height. +// let heightConstraint = NSLayoutConstraint( +// item: self.activityPanel.contentView!, +// attribute: .height, +// relatedBy: .equal, +// toItem: nil, +// attribute: .notAnAttribute, +// multiplier: 0.0, +// constant: 20.0) +// heightConstraint.priority = .defaultLow +// self.activityPanel.contentView?.addConstraint(heightConstraint) + // Inspect the activity panel for indicators. var activityIndicators: [NSLevelIndicator] = [] var textFields: [NSTextField] = [] From 62eef8cb407d2142ad2ba96da0dfacfbeb20220f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jun 2018 21:35:39 -0400 Subject: [PATCH 5/8] Reinstates proper ready behaviour. --- Storage/Disk/Drive.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 350449447..aef01b99e 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -113,7 +113,6 @@ bool Drive::get_is_read_only() { } bool Drive::get_is_ready() { - return true; return ready_index_count_ == 2; } From 685a80f95bab5abcee035b90b765f92054016c5e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jun 2018 21:49:57 -0400 Subject: [PATCH 6/8] Ensures the Electron Plus 3 properly announces drives to an activity observer. Does away with lazy allocation as not all that helpful, and liable to cause complexity. --- Machines/Electron/Electron.cpp | 4 ++++ Machines/Electron/Plus3.cpp | 19 ++++++++++--------- Machines/Electron/Plus3.hpp | 5 +++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 11b090a7b..ecdb5bf17 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -485,6 +485,10 @@ class ConcreteMachine: if(activity_observer_) { activity_observer_->register_led(caps_led); activity_observer_->set_led_status(caps_led, caps_led_state_); + + if(plus3_) { + plus3_->set_activity_observer(observer); + } } } diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp index 10ecf3787..75e08e3ab 100644 --- a/Machines/Electron/Plus3.cpp +++ b/Machines/Electron/Plus3.cpp @@ -11,14 +11,12 @@ using namespace Electron; Plus3::Plus3() : WD1770(P1770) { + drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2)); + drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2)); set_control_register(last_control_, 0xff); } -void Plus3::set_disk(std::shared_ptr disk, int drive) { - if(!drives_[drive]) { - drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2)); - if(drive == selected_drive_) set_drive(drives_[drive]); - } +void Plus3::set_disk(std::shared_ptr disk, size_t drive) { drives_[drive]->set_disk(disk); } @@ -42,8 +40,8 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) { } } if(changes & 0x04) { - if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0); - if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0); + drives_[0]->set_head((control & 0x04) ? 1 : 0); + drives_[1]->set_head((control & 0x04) ? 1 : 0); } if(changes & 0x08) set_is_double_density(!(control & 0x08)); } @@ -55,6 +53,9 @@ void Plus3::set_motor_on(bool on) { } void Plus3::set_activity_observer(Activity::Observer *observer) { - drives_[0]->set_activity_observer(observer, "Drive 1", true); - drives_[1]->set_activity_observer(observer, "Drive 2", true); + size_t index = 0; + for(const auto &drive: drives_) { + drive->set_activity_observer(observer, "Drive " + std::to_string(index+1), true); + ++index; + } } diff --git a/Machines/Electron/Plus3.hpp b/Machines/Electron/Plus3.hpp index 8033a940f..ab2486c9d 100644 --- a/Machines/Electron/Plus3.hpp +++ b/Machines/Electron/Plus3.hpp @@ -18,17 +18,18 @@ class Plus3 : public WD::WD1770 { public: Plus3(); - void set_disk(std::shared_ptr disk, int drive); + void set_disk(std::shared_ptr disk, size_t drive); void set_control_register(uint8_t control); void set_activity_observer(Activity::Observer *observer); private: void set_control_register(uint8_t control, uint8_t changes); - std::shared_ptr drives_[2]; + std::vector> drives_; int selected_drive_ = 0; uint8_t last_control_ = 0; void set_motor_on(bool on); + std::string drive_name(size_t drive); }; } From 1bdc718527412c13550cd121067bf1e1f8a4a0a2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jun 2018 22:15:52 -0400 Subject: [PATCH 7/8] Ensures the MSX reports the proper number of drives. --- Machines/MSX/DiskROM.cpp | 25 ++++++++----------------- Machines/MSX/DiskROM.hpp | 2 -- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/Machines/MSX/DiskROM.cpp b/Machines/MSX/DiskROM.cpp index 067c037b4..ddeb556db 100644 --- a/Machines/MSX/DiskROM.cpp +++ b/Machines/MSX/DiskROM.cpp @@ -13,6 +13,8 @@ using namespace MSX; DiskROM::DiskROM(const std::vector &rom) : WD1770(P1793), rom_(rom) { + drives_[0].reset(new Storage::Disk::Drive(8000000, 300, 2)); + drives_[1].reset(new Storage::Disk::Drive(8000000, 300, 2)); set_is_double_density(true); } @@ -23,16 +25,16 @@ void DiskROM::write(uint16_t address, uint8_t value, bool pc_is_outside_bios) { break; case 0x7ffc: selected_head_ = value & 1; - if(drives_[0]) drives_[0]->set_head(selected_head_); - if(drives_[1]) drives_[1]->set_head(selected_head_); + drives_[0]->set_head(selected_head_); + drives_[1]->set_head(selected_head_); break; case 0x7ffd: { selected_drive_ = value & 1; set_drive(drives_[selected_drive_]); bool drive_motor = !!(value & 0x80); - if(drives_[0]) drives_[0]->set_motor_on(drive_motor); - if(drives_[1]) drives_[1]->set_motor_on(drive_motor); + drives_[0]->set_motor_on(drive_motor); + drives_[1]->set_motor_on(drive_motor); } break; } } @@ -57,12 +59,6 @@ void DiskROM::run_for(HalfCycles half_cycles) { } void DiskROM::set_disk(std::shared_ptr disk, size_t drive) { - if(!drives_[drive]) { - drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2)); - drives_[drive]->set_head(selected_head_); - if(drive == selected_drive_) set_drive(drives_[drive]); - drives_[drive]->set_activity_observer(observer_, drive_name(drive), true); - } drives_[drive]->set_disk(disk); } @@ -72,14 +68,9 @@ void DiskROM::set_head_load_request(bool head_load) { } void DiskROM::set_activity_observer(Activity::Observer *observer) { - size_t c = 0; - observer_ = observer; + size_t c = 1; for(auto &drive: drives_) { - if(drive) drive->set_activity_observer(observer, drive_name(c), true); + drive->set_activity_observer(observer, "Drive " + std::to_string(c), true); ++c; } } - -std::string DiskROM::drive_name(size_t index) { - return "Drive " + std::to_string(index); -} diff --git a/Machines/MSX/DiskROM.hpp b/Machines/MSX/DiskROM.hpp index a32a599f0..316c63ccc 100644 --- a/Machines/MSX/DiskROM.hpp +++ b/Machines/MSX/DiskROM.hpp @@ -41,8 +41,6 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 { std::array, 2> drives_; void set_head_load_request(bool head_load) override; - std::string drive_name(size_t index); - Activity::Observer *observer_ = nullptr; }; } From adca86216674d6e98b0ba90717031e863bd21b43 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jun 2018 22:37:19 -0400 Subject: [PATCH 8/8] Finally makes an initial pass at logging macros. --- Components/1770/1770.cpp | 26 +++++----- Components/8272/i8272.cpp | 51 +++++++++---------- .../Clock Signal.xcodeproj/project.pbxproj | 2 + Outputs/Log.hpp | 31 +++++++++++ 4 files changed, 72 insertions(+), 38 deletions(-) create mode 100644 Outputs/Log.hpp diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 17d839962..1ba3a97ea 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -7,7 +7,9 @@ // #include "1770.hpp" + #include "../../Storage/Disk/Encodings/MFM/Constants.hpp" +#include "../../Outputs/Log.hpp" using namespace WD; @@ -25,10 +27,10 @@ void WD1770::set_register(int address, uint8_t value) { if((value&0xf0) == 0xd0) { if(value == 0xd0) { // Force interrupt **immediately**. - printf("Force interrupt immediately\n"); + LOG("Force interrupt immediately"); posit_event(static_cast(Event1770::ForceInterrupt)); } else { - printf("!!!TODO: force interrupt!!!\n"); + ERROR("!!!TODO: force interrupt!!!"); update_status([] (Status &status) { status.type = Status::One; }); @@ -193,7 +195,7 @@ void WD1770::posit_event(int new_event_type) { // Wait for a new command, branch to the appropriate handler. case 0: wait_for_command: - printf("Idle...\n"); + LOG("Idle..."); set_data_mode(DataMode::Scanning); index_hole_count_ = 0; @@ -209,7 +211,7 @@ void WD1770::posit_event(int new_event_type) { status.interrupt_request = false; }); - printf("Starting %02x\n", command_); + LOG("Starting " << std::hex << command_ << std::endl); if(!(command_ & 0x80)) goto begin_type_1; if(!(command_ & 0x40)) goto begin_type_2; @@ -327,7 +329,7 @@ void WD1770::posit_event(int new_event_type) { } if(header_[0] == track_) { - printf("Reached track %d\n", track_); + LOG("Reached track " << std::dec << track_); update_status([] (Status &status) { status.crc_error = false; }); @@ -396,20 +398,20 @@ void WD1770::posit_event(int new_event_type) { READ_ID(); if(index_hole_count_ == 5) { - printf("Failed to find sector %d\n", sector_); + LOG("Failed to find sector " << std::dec << sector_); update_status([] (Status &status) { status.record_not_found = true; }); goto wait_for_command; } if(distance_into_section_ == 7) { - printf("Considering %d/%d\n", header_[0], header_[2]); + LOG("Considering " << std::dec << header_[0] << "/" << header_[2]); set_data_mode(DataMode::Scanning); if( header_[0] == track_ && header_[2] == sector_ && (has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { - printf("Found %d/%d\n", header_[0], header_[2]); + LOG("Found " << std::dec << header_[0] << "/" << header_[2]); if(get_crc_generator().get_value()) { - printf("CRC error; back to searching\n"); + LOG("CRC error; back to searching"); update_status([] (Status &status) { status.crc_error = true; }); @@ -465,7 +467,7 @@ void WD1770::posit_event(int new_event_type) { distance_into_section_++; if(distance_into_section_ == 2) { if(get_crc_generator().get_value()) { - printf("CRC error; terminating\n"); + LOG("CRC error; terminating"); update_status([this] (Status &status) { status.crc_error = true; }); @@ -476,7 +478,7 @@ void WD1770::posit_event(int new_event_type) { sector_++; goto test_type2_write_protection; } - printf("Finished reading sector %d\n", sector_); + LOG("Finished reading sector " << std::dec << sector_); goto wait_for_command; } goto type2_check_crc; @@ -558,7 +560,7 @@ void WD1770::posit_event(int new_event_type) { sector_++; goto test_type2_write_protection; } - printf("Wrote sector %d\n", sector_); + LOG("Wrote sector " << std::dec << sector_); goto wait_for_command; diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 8cbc434c8..d45109ac5 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -7,9 +7,8 @@ // #include "i8272.hpp" -//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp" -#include +#include "../../Outputs/Log.hpp" using namespace Intel::i8272; @@ -115,7 +114,7 @@ void i8272::run_for(Cycles cycles) { while(steps--) { // Perform a step. int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; - printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); + LOG("Target " << std::dec << drives_[c].target_head_position << " versus believed " << drives_[c].head_position); select_drive(c); get_drive().step(Storage::Disk::HeadPosition(direction)); if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; @@ -386,17 +385,17 @@ void i8272::posit_event(int event_type) { // the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the // values in the internal registers. index_hole_limit_ = 2; -// printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_); +// LOG("Seeking " << std::dec << cylinder_ << " " << head_ " " << sector_ << " " << size_); find_next_sector: FIND_HEADER(); if(!index_hole_limit_) { // Two index holes have passed wihout finding the header sought. -// printf("Not found\n"); +// LOG("Not found"); SetNoData(); goto abort; } index_hole_count_ = 0; -// printf("Header\n"); +// LOG("Header"); READ_HEADER(); if(index_hole_count_) { // This implies an index hole was sighted within the header. Error out. @@ -407,11 +406,11 @@ void i8272::posit_event(int event_type) { // This implies a CRC error in the header; mark as such but continue. SetDataError(); } -// printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); +// LOG("Considering << std::hex << header_[0] << " " << header_[1] << " " << header_[2] << " " << header_[3] << " [" << get_crc_generator().get_value() << "]"); if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector; // Branch to whatever is supposed to happen next -// printf("Proceeding\n"); +// LOG("Proceeding"); switch(command_[0] & 0x1f) { case CommandReadData: case CommandReadDeletedData: @@ -425,7 +424,7 @@ void i8272::posit_event(int event_type) { // Performs the read data or read deleted data command. read_data: - printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); + LOG("Read [deleted] data [" << std::hex << command_[2] << " " << command_[3] << " " << command_[4] << " " << command_[5] << " ... " << command_[6] << " " << command_[8] << "]"); read_next_data: goto read_write_find_header; @@ -509,7 +508,7 @@ void i8272::posit_event(int event_type) { goto post_st012chrn; write_data: - printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); + LOG("Write [deleted] data [" << std::hex << command_[2] << " " << command_[3] << " " << command_[4] << " " << command_[5] << " ... " << command_[6] << " " << command_[8] << "]"); if(get_drive().get_is_read_only()) { SetNotWriteable(); @@ -544,7 +543,7 @@ void i8272::posit_event(int event_type) { goto write_loop; } - printf("Wrote %d bytes\n", distance_into_section_); + LOG("Wrote " << std::dec << distance_into_section_ << " bytes"); write_crc(); expects_input_ = false; WAIT_FOR_EVENT(Event::DataWritten); @@ -560,7 +559,7 @@ void i8272::posit_event(int event_type) { // Performs the read ID command. read_id: // Establishes the drive and head being addressed, and whether in double density mode. - printf("Read ID [%02x %02x]\n", command_[0], command_[1]); + LOG("Read ID [" << std::hex << command_[0] << " " << command_[1] << "]"); // Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. // If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. @@ -582,7 +581,7 @@ void i8272::posit_event(int event_type) { // Performs read track. read_track: - printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]); + LOG("Read track [" << std::hex << command_[2] << " " << command_[3] << " " << command_[4] << " " << command_[5] << "]"); // Wait for the index hole. WAIT_FOR_EVENT(Event::IndexHole); @@ -623,7 +622,7 @@ void i8272::posit_event(int event_type) { // Performs format [/write] track. format_track: - printf("Format track\n"); + LOG("Format track"); if(get_drive().get_is_read_only()) { SetNotWriteable(); goto abort; @@ -667,7 +666,7 @@ void i8272::posit_event(int event_type) { break; } - printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); + LOG("W: " << std::hex << header_[0] << " " << header_[1] << " " << header_[2] << " " << header_[3] << ", " << get_crc_generator().get_value()); write_crc(); // Write the sector body. @@ -699,15 +698,15 @@ void i8272::posit_event(int event_type) { goto post_st012chrn; scan_low: - printf("Scan low unimplemented!!\n"); + ERROR("Scan low unimplemented!!"); goto wait_for_command; scan_low_or_equal: - printf("Scan low or equal unimplemented!!\n"); + ERROR("Scan low or equal unimplemented!!"); goto wait_for_command; scan_high_or_equal: - printf("Scan high or equal unimplemented!!\n"); + ERROR("Scan high or equal unimplemented!!"); goto wait_for_command; // Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work @@ -738,11 +737,11 @@ void i8272::posit_event(int event_type) { // up in run_for understands to mean 'keep going until track 0 is active'). if(command_.size() > 2) { drives_[drive].target_head_position = command_[2]; - printf("Seek to %02x\n", command_[2]); + LOG("Seek to " << std::hex << command_[2]); } else { drives_[drive].target_head_position = -1; drives_[drive].head_position = 0; - printf("Recalibrate\n"); + LOG("Recalibrate"); } // Check whether any steps are even needed; if not then mark as completed already. @@ -755,7 +754,7 @@ void i8272::posit_event(int event_type) { // Performs sense interrupt status. sense_interrupt_status: - printf("Sense interrupt status\n"); + LOG("Sense interrupt status"); { // Find the first drive that is in the CompletedSeeking state. int found_drive = -1; @@ -783,7 +782,7 @@ void i8272::posit_event(int event_type) { // Performs specify. specify: // Just store the values, and terminate the command. - printf("Specify\n"); + LOG("Specify"); step_rate_time_ = 16 - (command_[1] >> 4); // i.e. 1 to 16ms head_unload_time_ = (command_[1] & 0x0f) << 4; // i.e. 16 to 240ms head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms @@ -794,7 +793,7 @@ void i8272::posit_event(int event_type) { goto wait_for_command; sense_drive_status: - printf("Sense drive status\n"); + LOG("Sense drive status"); { int drive = command_[1] & 3; select_drive(drive); @@ -833,11 +832,11 @@ void i8272::posit_event(int event_type) { // Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the // last thing in it will be returned first. post_result: - printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); + LOGNBR("Result to " << std::hex << (command_[0] & 0x1f) << ", main " << main_status_); for(std::size_t c = 0; c < result_stack_.size(); c++) { - printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); + LOGNBR(result_stack_[result_stack_.size() - 1 - c]); } - printf("\n"); + LOGNBR(std::endl); // Set ready to send data to the processor, no longer in non-DMA execution phase. is_executing_ = false; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 2b21d153e..06bbc3832 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1381,6 +1381,7 @@ 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = ""; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = ""; }; 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = ""; }; + 4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Log.hpp; path = ../../Outputs/Log.hpp; sourceTree = ""; }; 4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; }; 4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; @@ -1774,6 +1775,7 @@ children = ( 4B0CCC411C62D0B3001CAC5F /* CRT */, 4BD060A41FE49D3C006E14BE /* Speaker */, + 4BD601A920D89F2A00CBCE57 /* Log.hpp */, ); name = Outputs; sourceTree = ""; diff --git a/Outputs/Log.hpp b/Outputs/Log.hpp new file mode 100644 index 000000000..36f7e2fe9 --- /dev/null +++ b/Outputs/Log.hpp @@ -0,0 +1,31 @@ +// +// Log.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/06/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef Log_h +#define Log_h + +#ifdef NDEBUG + +#define LOG(x) +#define LOGNBR(x) +#define ERROR(x) +#define ERRORNBR(x) + +#else + +#include + +#define LOG(x) std::cout << x << std::endl +#define LOGNBR(x) std::cout << x +#define ERROR(x) std::cerr << x << std::endl +#define ERRORNBR(x) std::cerr << x + +#endif + + +#endif /* Log_h */