I have written a small application, initially under OSX 10.8, that receives MIDI events via MidiEventCallback and puts the data into a NSMutableArray that is a member of a NSTableViewDataSource.
In this video you can see how it worked without any problems on OSX 10.8.
Now I have a new MacBook that runs on OSX 10.9, installed XCode and got all my project files and compiled the application.
Have connected my MIDI controller to my MacBook, started the application and pressing keys on the MIDI controller.
The problem:
The NSTableView is not being updated as it should. If I press my piano keys, no MIDI messages will be displayed and after a couple of seconds I get an exception. Here is a screenshot of what XCode is showing me then:
I stop the application and start it again. Now, when pressing keys, I resize the application window continuously and the NSTableView does update properly, no exception, everything fine.
Same when I press some piano keys a couple of times. Then bring some other application to the front and then my application to the front again. Again, all MIDI messages are displayed properly in my NSTableView.
Any idea what this can be? Is it a OSX 10.9 related issue or could it be with changing to a new MacBook. Am running out of options.
What I have tried is connecting the NSWindow of my application as an IBOutlet to my controller that acts as a NSTableViewDataSource and tried the following calls right after I did [tableView reloadData]
[window update]
[window display]
[window flushWindow]
but nothing helped. Any help highly appreciated.
EDIT
Did some more debugging following the comments and actually was looking at the wrong place.
Here some code for more context:
My controller has a property called MidiEventListener that receives all MIDI events and puts them into eventList.
#interface MidiAidController()
...
#property NSMutableArray *eventList;
#property MidiEventListener* listener;
#end
In the init method of my controller I do the following
_eventList = [[NSMutableArray alloc] init];
MidiEventCallback eventCallback = ^(MidiEvent* midiEvent)
{
[[self eventList] addObject:midiEvent];
[[self tableView] reloadData];
};
...
self.listener = [[MidiEventListener alloc] initWithCallback:eventCallback];
In MidiEventListener, within initWithCallback, the following happens:
result = MIDIInputPortCreate(_midiClient, CFSTR("Input"), midiInputCallback, (__bridge_retained void *)(self), &_inputPort);
Now, let's go over to midiInputCallback:
static void midiInputCallback(const MIDIPacketList* list, void *procRef, void *srcRef)
{
MidiEventListener *midiEventListener = (__bridge MidiEventListener*)procRef;
#autoreleasepool {
const MIDIPacket *packet = &list->packet[0];
for (unsigned int i = 0; i < list->numPackets; i++)
{
MidiEvent* midiEvent = [[MidiEvent alloc] initWithPacket:packet];
midiEventListener.midiEventCallback(midiEvent);
packet = MIDIPacketNext(packet);
}
}
}
That is basically it. The Exception happens at midiEventListener.midiEventCallback(midiEvent);. I always looked at *[tableView reloadData] since that was the line when clicking under Thread 6 - 19__25... (see screenshot above). But when I click on Thread 6 - 20 midiInputCallback then I get this line highlighted.
SOLUTION
Reloading the data has to be done from the main thread:
MidiEventCallback eventCallback = ^(MidiEvent* midiEvent)
{
[[self eventList] addObject:midiEvent];
dispatch_async(dispatch_get_main_queue(), ^(void){[[self tableView] reloadData];});
};
*Thread 6 - 19__25...* and reloadData
call reload only on Main Thread
e.g.
void MyCallback(...) {
dispatch_async(dispatch_get_main_queue(), ^{
...
}
}
Related
My SpriteKit game has three scenes for now; Menu.m, LevelSelect.m and Level.m. When I start the app, the memory usage is 35MB. Upon transitioning from the main menu to the level selection scene it pretty much stays around 35MB. Moving from the level select scene to the level itself it shoots up to around 150MB. Granted, there are more sprites and classes involved in creating the level. However, when I reload the level via an overlay menu that I have as a sprite the memory usage continues to rise by around 2MB for each reload. Could it be a retain cycle issue? The same is true if I switch back to the level select scene and then re-enter the level itself, the memory continues to climb and climb. I am concerned about my implementation of my state machine. I'll post some skeletal code below:
LevelScene.m
-(void)didMoveToView:(SKView *)view {
...
[self initGameStateMachine];
...
}
...
//other methods for setting up the level present
...
-(void) initGameStateMachine {
LevelSceneActiveState *levelSceneActiveState = [[LevelSceneActiveState alloc] initLevelScene:self];
LevelSceneConfigureState *levelSceneConfigureState = [[LevelSceneConfigureState alloc] initLevelScene:self];
LevelSceneFailState *levelSceneFailState = [[LevelSceneFailState alloc] initLevelScene:self];
LevelSceneSuccessState *levelSceneSuccessState = [[LevelSceneSuccessState alloc] initLevelScene:self];
NSMutableDictionary *states = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
levelSceneActiveState, #"LevelSceneActiveState",
levelSceneConfigureState, #"LevelSceneConfigureState",
levelSceneFailState, #"LevelSceneFailState",
levelSceneSuccessState, #"LevelSceneSuccessState",
nil];
_gameStateMachine = [[StateMachine alloc] initWithStates:states];
[_gameStateMachine enterState:levelSceneActiveState];
}
-(void)update:(CFTimeInterval)currentTime {
//update the on screen keyboard with notes that are being played
[_gameStateMachine updateWithDeltaTime:[NSNumber numberWithDouble:currentTime]];
}
-(void) willMoveFromView:(SKView *)view {
[self removeAllChildren];
}
StateMachine.m
#import "StateMachine.h"
#import "GameState.h"
#interface StateMachine()
#property NSMutableDictionary *states;
#end
#implementation StateMachine
-(instancetype)initWithStates:(NSMutableDictionary*)states {
if (self = [super init]) {
for (id key in [states allValues]) {
if (![key conformsToProtocol:#protocol(GameState)]) {
NSLog(#"%# does not conform to #protocol(GameState)", key);
return nil;
}
}
_states = states;
}
return self;
}
//this method will be used to start the state machine process
-(bool)enterState:(id)nextState {
if (!_currentState) {
_currentState = [_states objectForKey:[nextState className]];
[[NSNotificationCenter defaultCenter] addObserver:_currentState selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil];
return YES;
}
else if ([_currentState isValidNextState:nextState]) {
[_currentState performSelector:#selector(willLeaveState)];
_currentState = [_states objectForKey:[nextState className]];
[[NSNotificationCenter defaultCenter] addObserver:_currentState selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil];
return YES;
}
return NO;
}
-(void)updateWithDeltaTime:(NSNumber*)currentTime {
[_currentState performSelector:#selector(updateWithDeltaTime:) withObject:currentTime];
}
#end
LevelSceneActiveState //this is one of the 4 states
#import "LevelSceneActiveState.h"
#import "LevelSceneConfigureState.h"
#import "LevelSceneSuccessState.h"
#import "LevelSceneFailState.h"
#import "StateMachine.h"
#import "LevelScene.h"
#import "SSBitmapFontLabelNode.h"
#interface LevelSceneActiveState()
#property LevelScene *levelScene;
#end
#implementation LevelSceneActiveState
-(instancetype)initLevelScene:(LevelScene *)levelScene {
if (self = [super init])
_levelScene = levelScene;
return self;
}
-(void)updateWithDeltaTime:(NSNumber*)currentTime {
//game variables created here
....
//state machine needs to be set here...if set in init, it does not have a value in the LevelScene yet
if (_gameStateMachine == nil)
_gameStateMachine = _levelScene.gameStateMachine;
//game logic performed here
...
//check for timer finishing
if (!_levelScene.timer.isValid) {
//success
if (_levelScene.score >= 7) {
[_gameStateMachine enterState:LevelSceneSuccessState.self];
}
else { //failure
[_gameStateMachine enterState:LevelSceneFailState.self];
}
}
}
//another class is used to trigger notifications of key presses
-(void) keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[#"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
if (keyCode == 53)
[self escapePressed];
}
-(void) escapePressed {
[_gameStateMachine enterState:LevelSceneConfigureState.self];
[_levelScene childNodeWithName:#"timer"].paused = YES;
}
-(bool)isValidNextState:(id)nextState {
if ([[nextState className] isEqualToString:#"LevelSceneConfigureState"])
return YES;
...
return NO;
}
//this makes sure that we're not notifying an object that may not exist
-(void) willLeaveState {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:#"KeyPressedNotificationKey"
object:nil];
}
#end
When switching between states I do not want the LevelScene scene to go away. I understand that this is hard to diagnose especially when you're not sitting in front of the full project. What can I do to self-diagnose this myself? Any helpful tips/tricks would be great.
[UPDATE] I tried the Product->Profile->Instruments->Allocation thing, but I have no idea what to do with it. The memory is indicating that it continues to rise though.
Instrument Tutorial Article
For people that are as clueless as me when it comes to using Xcode's Instruments, here is an amazing article from Ray Wenderlich that really dumbs it down!
I found so many issues in my project that I didn't even think were issues because of that article. I'm not surprised that no one posted an answer regarding this question because when you have these types of issues they are very personal to the project that you are working on and quite hard to debug.
My Issue + Solution
My issue is a common one. I was loading a set of resources, in this case a .sf2 (sound font) file, over and over and over again when my scene reloaded. I honestly thought I had gotten rid of that issue with all of my sprites.
Here's how I found it using Instruments:
In Xcode go to Product->Profile then select Allocations
This window should pop up:
Click on the red circle button at the top left (this will start your app)
Perform the operations in your app that seem to cause issues then press Mark Generation
Repeat the operations that cause the issues and continue to press Mark Generation
Note: Mark Generation takes a snapshot of the app at that time so that you can see the changes in memory
Stop the app from running and then dive into one of the generations (I chose Generation C, because that's when the differences in memory usage became constant)
My sampler (an AVAudioUnitSampler object) shown as SamplerState is allocating a bunch of memory
I clicked on the small arrow to the right of SamplerState and it brought me into this view (shows a ton of items that are allocated memory)
Clicking on one of them plus clicking on the extended details button will allow you to see the stack trace for this item
I double clicked on the method that I thought would be the issue
After double clicking on the method, it brings up another view with your code in it along with percentages of what lines of code allocate the most memory (extremely helpful!)
Note: The culprit, in my case, was allocating roughly 1.6MB every time I reloaded the level! Ignore the commented out code, I took the screen shot of a saved session after I fixed the issue.
After fixing my code there is no longer any major memory allocations..although I still have some things to clean up! Only 33KB between level reloads, which is much better!
I am trying to detect and use a gamepad attached to my Mac in my game.
I am using the IOKit for this but without success.
The gamepad is recognized as I downloaded a Mac app that views all gamepads attached.
It is a Logitech F310 and I have set it to DirectInput.
I am calling setupInput inside awakeFromNib but none of the callbacks are ever called.
There is no error or exception thrown.
I have no idea why the callbacks are not called.
What am I doing wrong?
I am thinking maybe it's an issue with the run loop. I tried both CFRunLoopGetMain and CFRunLoopGetCurrent.
Not sure what else to do.
Edit: As suggested by someone I tried to put the same GamePad code in a new project and it worked.
There is some difference in the structure of the classes but I couldn't make it work in my app even if I tried to replicate the classes.
My app use AppDelegate and NSOpenGLView. Strangely enough it doesn't have an NSViewController.
The minimal app use NSOpenGLView but also NSResponder and when I put the gamepad code inside the(custom) NSResponder class it "works"(detects the gamepad and responds to input).
I tried to add that custom class to my app but it "didn't work".
What am I missing?
void gamepadWasAdded(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
NSLog(#"Gamepad was plugged in");
}
void gamepadWasRemoved(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
NSLog(#"Gamepad was unplugged");
}
void gamepadAction(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
NSLog(#"Gamepad talked!");
IOHIDElementRef element = IOHIDValueGetElement(value);
NSLog(#"Element: %#", element);
int elementValue = IOHIDValueGetIntegerValue(value);
NSLog(#"Element value: %i", elementValue);
}
-(void) setupInput {
//get a HID manager reference
hidManager = IOHIDManagerCreate(kCFAllocatorDefault,
kIOHIDOptionsTypeNone);
//define the device to search for, via usage page and usage key
NSMutableDictionary* criterion = [[NSMutableDictionary alloc] init];
[criterion setObject: [NSNumber numberWithInt: kHIDPage_GenericDesktop]
forKey: (NSString*)CFSTR(kIOHIDDeviceUsagePageKey)];
[criterion setObject: [NSNumber numberWithInt: kHIDUsage_GD_Joystick]
forKey: (NSString*)CFSTR(kIOHIDDeviceUsageKey)];
//search for the device
IOHIDManagerSetDeviceMatching(hidManager,
(__bridge CFDictionaryRef)criterion);
//register our callback functions
IOHIDManagerRegisterDeviceMatchingCallback(hidManager, gamepadWasAdded,
(__bridge void*)self);
IOHIDManagerRegisterDeviceRemovalCallback(hidManager, gamepadWasRemoved,
(__bridge void*)self);
IOHIDManagerRegisterInputValueCallback(hidManager, gamepadAction,
(__bridge void*)self);
//scedule our HIDManager with the current run loop, so that we
//are able to recieve events from the hardware.
IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetMain(),
kCFRunLoopDefaultMode);
//open the HID manager, so that it can start routing events
//to our callbacks.
IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
}
// Put our timer in -awakeFromNib, so it can start up right from the beginning
-(void)awakeFromNib
{
renderTimer = [NSTimer timerWithTimeInterval:0.001 //a 1ms time interval
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:renderTimer
forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:renderTimer
forMode:NSEventTrackingRunLoopMode]; //Ensure timer fires during resize
[self setupInput];
}
Ok, I have found the issue.
The problem was that my Mac app was in sandbox and I didn't check mark the Hardware/USB check box.
With this option checked it works with the original code.
I'm creating a piano app (for OSX) that has an onscreen keyboard that displays what the user is playing on their synth keyboard. Everything is connected using the CoreMIDI framework. I have created some customs buttons by subclassing NSButton in a class called PianoKey. The class can be seen below:
#import "PianoKey.h"
#implementation PianoKey
//updates the core animation layer whenever it needs to be changed
- (BOOL)wantsUpdateLayer {
return YES;
}
- (void)updateLayer {
//tells you whether or not the button is pressed
if ([self.cell isHighlighted]) {
NSLog(#"BUTTON PRESSED");
self.layer.contents = [NSImage imageNamed:#"buttonpressed.png"];
}
else {
NSLog(#"BUTTON NOT PRESSED");
self.layer.contents = [NSImage imageNamed:#"button.png"];
}
}
#end
The "PianoKeys" are created programmatically. When unpressed, the piano keys are blue and when they are pressed they are pink (just temp colours). The colour switch works fine if I click the buttons on screen, but they are not changing when I try to play through my MIDI keyboard.
Some things to note:
1) The MIDI keyboard works
2) I am successfully getting MIDI data from the keyboard.
a) This MIDI data is passed to a callback function called midiInputCallback as seen below:
[from class AppController.m]
void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
PianoKey *button = (__bridge PianoKey*)procRef;
UInt16 nBytes;
const MIDIPacket *packet = &list->packet[0]; //gets first packet in list
for(unsigned int i = 0; i < list->numPackets; i++) {
nBytes = packet->length; //number of bytes in a packet
handleMIDIStatus(packet, button);
packet = MIDIPacketNext(packet);
}
}
The object button is a reference to the button that I've created programatically as defined in AppController.h as:
#property PianoKey *button; //yes, this is synthesized in AppController.m
b) The callback function calls a bunch of functions that handle the MIDI data. If a note has been detected as being played, this is where I set my custom button to be highlighted...this can be seen in the function below:
void updateKeyboardButtonAfterKeyPressed(PianoKey *button, int key, bool keyOn) {
if(keyOn)
[button highlight:YES];
else
[button highlight:NO];
[button updateLayer];
}
[button updateLayer] calls my updateLayer() method from PianoKey, but it doesn't change the image. Is there something I'm missing? It seems as if the view or window is not being updated, even though my button says that it is "highlighted". Please help! Thanks in advance :)
Just a guess (but a good one...;-) Is the callback function being called on the main thread? If not, that could be the problem - at leas that is the way it works in iOS: the UI (screen drawing) is only updated in the main thread.
Put a log statement or breakpoint in the callback to see if it is getting called. If it is, then the problem is due to this thread issue.
Update:
So tell the button to update itself - but on the main thread (I think I have the syntaxt right - at least for iOS - OSX may vary)
[button performSelectorOnMainThread:#selector(updateLayer)
withObject:nil
waitUntilDone:FALSE];
I am attempting to make a basic game which requires a serious of buttons to control player movement. Keep in mind I am using cocos-2d. My goal is to have the buttons be holdable and move a sprite when held down. The code i am using now looks like this.
CCMenuItemHoldable.h
#interface CCMenuItemSpriteHoldable : CCMenuItemSprite {
bool buttonHeld;
}
#property (readonly, nonatomic) bool buttonHeld;
CCMenuItemHoldable.m
#implementation CCMenuItemSpriteHoldable
#synthesize buttonHeld;
-(void) selected
{
[super selected];
buttonHeld = true;
[self setOpacity:128];
}
-(void) unselected
{
[super unselected];
buttonHeld = false;
[self setOpacity:64];
}
#end
and for the set up of the buttons
rightBtn = [CCMenuItemSpriteHoldable itemFromNormalSprite:[CCSprite spriteWithFile:#"art/hud/right.png"] selectedSprite:[CCSprite spriteWithFile:#"art/hud/right.png"] target:self selector:#selector(rightButtonPressed)];
CCMenu *directionalMenu = [CCMenu menuWithItems:leftBtn, rightBtn, nil];
[directionalMenu alignItemsHorizontallyWithPadding:0];
[directionalMenu setPosition:ccp(110,48)];
[self addChild:directionalMenu];
This all seems to work fine but when i do
-(void)rightButtonPressed:(id) sender
{
if([sender buttonHeld])
targetX = 10;
else{
targetX = 0;
}
}
The crash has been fixed but I am trying to get my sprite to move. In my game tick function I add the value of targetX to the position of the sprite on a timer, still no movement.
Please, always include a crash log when asking questions about crashes.
In your case, I can guess the problem. You are adding this selector:
#selector(rightButtonPressed)
Your method is called
rightButtonPressed:(id)sender
Which would be, as a selector, rightButtonPressed: - note the colon indicating that an argument is passed. Either change the method so it has no argument, or add a colon to the selector when you create the button.
The crash log would be telling you this - it would say "unrecognised selector sent to..." with the name of the receiving class, and the name of the selector.
I have a small png that I am adding to a view which I am pretty sure I had working previously but suddenly stopped working on the iPad itself while continuing to work fine on the iPad simulator.
Here is the code I am using to add the image to the view...
UIImageView *bottomResizer = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"resizeLine.png"]];
bottomResizer.center = CGPointMake(bottomResizer.center.x, bottomResizer.center.y+self.frame.size.height-12);
bottomResizer.tag = 301;
[self addSubview:bottomResizer];
[bottomResizer release];
This occurs in a UIGestureRecognizerStateBegan event. The following code in removes the image in a touchesEnded event without any errors even though you cannot see it.
NSArray *subViews = [self subviews];
int count = [subViews count];
for (int i =count-1; i>=0; i--) {
if([[subViews objectAtIndex:i] tag] == 301) {
[[subViews objectAtIndex:i] removeFromSuperview];
}
}
I don't think it is anything I changed in my code since it works in the simulator. Not sure where to look next for the problem. I have reset the simulator to see if it would break after a reset. I have also cleaned the project.
Thanks.
John
Put a breakpoint AFTER this line UIImageView *bottomResizer = [[UIImageView alloc]...
Then on the console "po [bottomResizer image]"
If it is nil then either the resource is not getting copied correctly into the bundle OR you could have a corrupt image that the device cannot load.