Encoding and Decoding GameState Class Issue - c++

I have a class named HeroState that is an inheritance from another class named CharacterProperties that contains armorId, headId, earsId and some other common values to create a character. In this new class I have declared on the header. (It is the class in charge of the players progress , by saying that It's a singleton)
#interface HeroState : CharacterProperties <NSCoding> {
ControlsType controlsType;
characterJob charJob;
MonsterJob monsterJob;
}
#property MonsterJob monsterJob;
#property characterJob charJob;
#property (readwrite)ControlsType controlsType;
+ (HeroState *) sharedInstance;
- (void)save;
#end
And I have a "database" class in charge of saving and loading the class.
database Header :
#import <Foundation/Foundation.h>
id loadData(NSString * filename);
void saveData(id theData, NSString *filename);
database Implementation :
NSString * pathForFile(NSString *filename) {
// 1
NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
// 2
NSString *documentsDirectory = [paths objectAtIndex:0];
// 3
return [documentsDirectory
stringByAppendingPathComponent:filename];
}
id loadData(NSString * filename) {
// 4
NSString *filePath = pathForFile(filename);
// 5
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
// 6
NSData *data = [[[NSData alloc]
initWithContentsOfFile:filePath] autorelease];
// 7
NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc]
initForReadingWithData:data] autorelease];
// 8
id retval = [unarchiver decodeObjectForKey:#"Data"];
[unarchiver finishDecoding];
return retval;
}
return nil;
}
void saveData(id theData, NSString *filename) {
// 9
NSMutableData *data = [[[NSMutableData alloc] init] autorelease];
// 10
NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc]
initForWritingWithMutableData:data] autorelease];
// 11
[archiver encodeObject:theData forKey:#"Data"];
[archiver finishEncoding];
// 12
[data writeToFile:pathForFile(filename) atomically:YES];
}
But then when I use it on my HeroClass
+(HeroState*)sharedInstance {
#synchronized([HeroState class])
{
if(!sharedInstance) {
sharedInstance = [loadData(#"HeroState") retain];
if (!sharedInstance) {
[[self alloc] initWithNewStats];
}
}
return sharedInstance;
}
return nil;
}
And here for saving
- (void)save {
saveData(self, #"HeroState");
}
It gives me an error when I try to build it:
Undefined symbols for architecture i386:
"loadData(NSString*)", referenced from:
+[HeroState sharedInstance] in HeroState.o
"saveData(objc_object*, NSString*)", referenced from:
-[HeroState save] in HeroState.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
I do have a initWithCoder function and a encodeWithCoder function that stick to the protocols of the NSCoding. So what could be wrong? I had this class as a NSObject inhertied class before and had no problem about it , but when I decided to recreate it being a inheritance of my CharacterProperties class that was an Inheritance of NSObject problems occurred.
I will appreciate any type of help!

Are you certain you wanted to declare these as C functions?
NSString * pathForFile(NSString *filename) {..}
id loadData(NSString * filename) {..}
void saveData(id theData, NSString *filename) {..}
Perhaps you should rather declare them as static class functions.
+(id) loadData:(NSString *)filename {..}
// similar for the other C functions
And call it accordingly:
sharedInstance = [[HeroState loadData:#"HeroState"] retain];
PS: please start using ARC. There's no reason not to in this day and age.

Related

AVAssetWriter fails with AVAssetWriterStatusFailed when reused

I am trying to use the AVAssetWriter to encode video. I am using ARC. What I am trying to do is stream video over a socket.
Now here is the problem. The setup I have works the first time around. I am unable to use the AVAssetWriter again, without restarting my application first. If I don't restart my application, when I call [m_writer startWriting] I receive the following error:
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x14d06fe0 {Error Domain=NSOSStatusErrorDomain Code=-12983 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-12983)}
I made sure that file was deleted when the cleaup method was called.
I used iExplorer to navigate to the temp folder where the file was
being created. I also made sure that the status of the AVAssetWriter
after calling finshWriting is AVAssetWriterStatusFinished.
Edit: The error persists even If I used a different file name.
I create an instance of the VideoEncoder class ( code below) and then I feed it raw images. Then I wait for something to be written to the output file by the AVAssetWriter using a dispatch_source_set_event_handler(), i.e.:
m_inputFile = [NSFileHandle fileHandleForReadingAtPath: m_writer.path];
m_readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, [m_inputFile fileDescriptor], 0, m_readQueue);
dispatch_source_set_event_handler(m_readSource, ^{
// Read bytes from file and send over socket
}
dispatch_source_set_cancel_handler(m_readSource, ^{
[self cleanup];
}
dispatch_resume(m_readSource);
I have created a wrapper for the AVAssetWriter called VideoEncoder. Here is how I initialize the AVAssetWriter:
#implementation VideoEncoder : NSObject
{
AVAssetWriter* m_writer;
AVAssetWriterInput* m_writerInput;
AVAssetWriterInputPixelBufferAdaptor* m_pixelBufferAdaptor;
}
-(id) initWithPath:(NSString*)path width:(int)width height:(int)height parameters:(NSDictionary*) parameters
{
if (self = [super init])
{
self.path = path;
[[NSFileManager defaultManager] removeItemAtPath:self.path error:nil];
NSURL* url = [NSURL fileURLWithPath: self.path];
m_writer = [[AVAssetWriter alloc] initWithURL:url fileType:AVFileTypeQuickTimeMovie error: nil];
NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys:
AVVideoCodecH264, AVVideoCodecKey,
[NSNumber numberWithInt: width], AVVideoWidthKey,
[NSNumber numberWithInt:height], AVVideoHeightKey,
parameters, AVVideoCompressionPropertiesKey,
nil];
m_writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
m_writerInput.expectsMediaDataInRealTime = YES;
[m_writer addInput:m_writerInput];
NSDictionary* pixelBufferOptions = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt: kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
[NSNumber numberWithInt: width*4], kCVPixelBufferBytesPerRowAllignmentKey,
[NSNumber numberWithInt; width], kCVPixelBufferWidthKey,
[NSNumber numberWithInt; height], kCVPixelBufferHeightKey,
nil];
m_pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
initWithAssetWriterInput: m_writerInput
sourcePixelBufferAttributes: pixelBufferOptions];
[m_writer startWriting];
[m_writer startSessionAtSourceTime: kCMTimeZero];
}
}
This is how I feed it raw images:
-(BOOL) encodeFrame:(CVPixelBufferRead)pixelBuffer time(CMTime)time;
{
if (m_writer.status == AVAssetWriterStatusFailed)
{
return NO;
}
if (m_writerInput.readyForMoreMediaData == YES)
{
if ([m_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:time])
{
return YES:
}
}
return NO;
}
This is how the pixel buffers are generated:
-(bool) encodeImage:(const char*) frameBuffer width:(int)width height:(int)height
{
CVPixelBufferRef pixelBuffer = NULL;
CVReturn ret;
ret = CVPixelBufferCraeteWithBytes(
kCFAllocatorDefault,
with,
height,
kCVPixelFormatType_32BGRA,
(void*) frameBuffer,
width * 4,
NULL, NULL, NULL, NULL, &pixelBuffer);
if (ret == kCVReturnSuccess && pixelBuffer)
{
CFTimeInterval currTime = CACurrentMediaTime();
if (m_frameIndex > 0 )
{
int ticks = (int)((currTime - m_prevTime) * 1000) / 30;
CMTime addTime;
ticks = ticks <= 0 ? 1 : ticks;
addTime = CMTimeMake(ticks, 30);
m_timestamp = CMTimeAdd(m_timestamp, addTime);
}
m_prevTime = currTime;
[m_impl encodeFrame: pixelBuffer time: m_timestamp];
CVBufferRelease(pixelBuffer);
m_frameIndex ++;
}
}
This is how I terminate the AVAssetWriter:
-(void) finishWritingWithCompletionHandler(void (^)(void))handler
{
[m_writerInput markAsFinished];
[m_writer finishWithCompletionHandler: ^{
handler();
}
}
And here is how I use the wrapper. I use this wrapper form a C++ class. Instantiation:
-(void) makeFilename
{
NSString* filename = [NSString* stringWithFormat: #"temp%d.tmp", 1];
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent: filename];
return path;
}
bool CFrameEncoder::StartEncoding()
{
m_impl = CFRetain((__bridge*void)[[VideoEncoder alloc] initWithPath:[self makeFilepath] width:800 height:480 parameters: params]);
}
Dealloc:
bool CFrameEncoder::StopDecoding()
{
[(__bridge VideoEncoder*) m_impl terminate];
CFRelease(m_impl);
}
The terminate function (of the VideoEncoder class):
-(void) terminate
{
if (m_readSource)
{
dispatch_source_cancel(m_readSource);
}
}
// The dispatch source cancel handler
-(void) cleanup
{
m_readQueue = nil;
m_readSource = nil;
[m_inputFile closeFile];
NSString* path = m_writer.path;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[m_writer finishWithCompletionHandler:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
m_writer = nil;
[[NSFileManager defaultManager] removeItemAtPath: path error: nil];
}
The AVAssetWriter class can have only a limited number of instances. If your application goes over this limit, then the AVAssetWriter begins to fail with AVAssetWriterStatusFailed when you try to startWriting.
This means that there is something in my code that is preventing the AVAssetWriter from being deallocated. Does anybody know where?

openCv IOS -No viable Overloaded '='

I am running an example code form a book, it is about using openCv on IOS device to do video processing. but I got "No viable overloaded '='" error, and i did search the StackOverFlow and found some similar posts and answers, but all solutions does not work for me, so I post code as below, and hope anyone can give some suggestions. Really appreciate it!
This is ViewController.h file:
#import <UIKit/UIKit.h>
#import <opencv2/imgcodecs/ios.h>
#import "CvEffects/RetroFilter.hpp"
#import <opencv2/videoio/cap_ios.h>
#interface ViewController : UIViewController<CvVideoCameraDelegate>
{
CvVideoCamera* videoCamera;
BOOL isCapturing;
RetroFilter::Parameters params;
cv::Ptr<RetroFilter> filter;
uint64_t prevTime;
}
#property (nonatomic, strong) CvVideoCamera* videoCamera;
#property (nonatomic, strong) IBOutlet UIImageView* imageView;
#property (nonatomic, strong) IBOutlet UIToolbar* toolbar;
#property (nonatomic, weak) IBOutlet
UIBarButtonItem* startCaptureButton;
#property (nonatomic, weak) IBOutlet
UIBarButtonItem* stopCaptureButton;
-(IBAction)startCaptureButtonPressed:(id)sender;
-(IBAction)stopCaptureButtonPressed:(id)sender;
#end
This is the ViewController.m file:
#import "ViewController.h"
#import <mach/mach_time.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize imageView;
#synthesize startCaptureButton;
#synthesize toolbar;
#synthesize videoCamera;
- (void)viewDidLoad
{
[super viewDidLoad];
// Initialize camera
videoCamera = [[CvVideoCamera alloc]
initWithParentView:imageView];
videoCamera.delegate = self;
videoCamera.defaultAVCaptureDevicePosition =
AVCaptureDevicePositionFront;
videoCamera.defaultAVCaptureSessionPreset =
AVCaptureSessionPreset352x288;
videoCamera.defaultAVCaptureVideoOrientation =
AVCaptureVideoOrientationPortrait;
videoCamera.defaultFPS = 30;
isCapturing = NO;
// Load textures
UIImage* resImage = [UIImage imageNamed:#"scratches.png"];
UIImageToMat(resImage, params.scratches);
resImage = [UIImage imageNamed:#"fuzzy_border.png"];
UIImageToMat(resImage, params.fuzzyBorder);
filter = NULL;
prevTime = mach_absolute_time();
}
- (NSInteger)supportedInterfaceOrientations
{
// Only portrait orientation
return UIInterfaceOrientationMaskPortrait;
}
-(IBAction)startCaptureButtonPressed:(id)sender
{
[videoCamera start];
isCapturing = YES;
params.frameSize = cv::Size(videoCamera.imageWidth,
videoCamera.imageHeight);
if (!filter)
filter = new RetroFilter(params);
}
-(IBAction)stopCaptureButtonPressed:(id)sender
{
[videoCamera stop];
isCapturing = NO;
}
//TODO: may be remove this code
static double machTimeToSecs(uint64_t time)
{
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
return (double)time * (double)timebase.numer /
(double)timebase.denom / 1e9;
}
// Macros for time measurements
#if 1
#define TS(name) int64 t_##name = cv::getTickCount()
#define TE(name) printf("TIMER_" #name ": %.2fms\n", \
1000.*((cv::getTickCount() - t_##name) / cv::getTickFrequency()))
#else
#define TS(name)
#define TE(name)
#endif
- (void)processImage:(cv::Mat&)image
{
cv::Mat inputFrame = image;
BOOL isNeedRotation = image.size() != params.frameSize;
if (isNeedRotation)
inputFrame = image.t();
// Apply filter
cv::Mat finalFrame;
TS(ApplyingFilter);
filter->applyToVideo(inputFrame, finalFrame);
TE(ApplyingFilter);
if (isNeedRotation)
finalFrame = finalFrame.t();
// Add fps label to the frame
uint64_t currTime = mach_absolute_time();
double timeInSeconds = machTimeToSecs(currTime - prevTime);
prevTime = currTime;
double fps = 1.0 / timeInSeconds;
NSString* fpsString =
[NSString stringWithFormat:#"FPS = %3.2f", fps];
cv::putText(finalFrame, [fpsString UTF8String],
cv::Point(30, 30), cv::FONT_HERSHEY_COMPLEX_SMALL,
0.8, cv::Scalar::all(255));
finalFrame.copyTo(image);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if (isCapturing)
{
[videoCamera stop];
}
}
- (void)dealloc
{
videoCamera.delegate = nil;
}
#end
And I got the error at two statements:
filter = NULL;
and
filter = new RetroFilter(params);
First issue, assigning the pointer:
filter = Ptr<RetroFilter>(new RetroFilter(params));
Second issue, Nulling the pointer:
filter = cv::Ptr<RetroFilter>::Ptr();
The reason is that the cv::Ptr object does not have overrides that make this simpler. The standard library's smart pointer class does a much nicer job of being easy to use.
The first issue is that the only = operator provided is this:
Ptr& operator = (const Ptr& ptr);
This means that you can't assign a RetroFilter to it, only another cv::Ptr, so you need to wrap the RetroFilter already.
The second issue, similar to the first, there is no override = operator that takes NULL. The best way to express a null cv::Ptr is
cv::Ptr<RetroFilter>::Ptr();
Which, being an instance of cv::Ptr can be assigned using the '=' operator.
Glad I could help!
Many thanks to #KirkSpaziani.
I figured that following code works, but do not know why?
Try filter = cv::Ptr<RetroFilter>(new RetroFilter(params);
filter = cv::Ptr<RetroFilter>::Ptr()

Why I often got ExcBadAccess in this code:

I uses ARC
NSAttributedString * arString = [self asSchedule:arTimes];
self.lblTimeLeft.attributedText = arString;
Not much information is given. It goes straight to main
int main(int argc, char *argv[])
{
#autoreleasepool {
//int retVal = UIApplicationMain(argc, argv, nil, nil);
int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([newIsiKotaAppDelegate class]));
return retVal;
}
}
I manage to spot that the last code executed is this:
self.lblTimeLeft.attributedText = arString;
I provided additional code to test things out
NSAttributedString * arString = [self asSchedule:arTimes];
self.lblTimeLeft.attributedText = arString;
PO1(#"Hello World");
while(false);// Error happen after this code is executed
}
The code is part of the routine that provides UITableViewCell to display. So error happen after
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = indexPath.row;
NSDictionary * rowDictionary = _marManagedObjectArray [row];
BGCinemaScheduleCell * BCS = [[BGCinemaScheduleCell alloc]init];
BCS.dicCinemaDictionary =rowDictionary;
return BCS; //This is where the error happen
}
It seems that the NSAttributedString works fine till it's actually displayed or something.
The error seems to happen on
TAttributes::TAttributes(__CFDictionary const *)
The content of asSchedule is usual
-(NSAttributedString *) asSchedule:(NSArray *) arTimes
{
NSMutableArray * timeBefore = [NSMutableArray array];
NSMutableArray * timeAfter = [NSMutableArray array];
for (NSString * strTime in arTimes) {
NSDate * date = [NSDate dtWithCurrentDateAndProvidedTime: strTime];
NSTimeInterval dblInterval =[date timeIntervalSinceNow];
if (dblInterval>0)
{
[timeAfter addObject:strTime];
}
else{
[timeBefore addObject:strTime];
}
}
NSString * strTimeBefore = [timeBefore componentsJoinedByString:#" "];
NSString * strTimeAfter = [timeAfter componentsJoinedByString:#" "];
NSString *yourString = [NSString stringWithFormat:#"%# %#", strTimeBefore, strTimeAfter];
// start at the end of strTimeBefore and go the length of strTimeAfter
NSRange boldedRange = NSMakeRange([strTimeBefore length] + 1, [strTimeAfter length]);
NSString *boldFontName = [[UIFont boldSystemFontOfSize:12] fontName];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:yourString];
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
PO1(NSStringFromRange(boldedRange));
PO1(#(attrString.length));
return attrString;
}
Update:
I changed self.lblTimeLeft.attributedText = arString; to self.lblTimeLeft.attributedText = arString.copy; to no avail.
I wonder if this could be the problem:
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
I think the original sample use KCFont something instead of NSFontAttributeName
You were almost there:
I wonder if this could be the problem: [attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
Your code says:
NSString *boldFontName = [[UIFont boldSystemFontOfSize:12] fontName];
...
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
It should say:
UIFont *boldFont = [UIFont boldSystemFontOfSize:12];
...
[attrString addAttribute:NSFontAttributeName value:boldFont range:boldedRange];
According to Table 1 of the docs (link below) and my own app code, you should be passing an NSFont not a font name.
https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/AttributedStrings/Articles/standardAttributes.html#//apple_ref/doc/uid/TP40004903-SW2
Can't tell from the code whether your project uses ARC or not, but it looks like your asSchedule method returns a string with a refcount of 0. It's up to you to retain a reference to that string, either old-school using the retain keyword (probably a bad idea) or by assigning it to a property declared with the strong keyword (or retain, if pre-ARC).
The best tool for debugging this stuff is Instruments, using the Zombies inspection. If I am right that you are trying to use the string after its refcount hits 0 and the memory has been released, you'll see the history of refcount increments and decrements pretty clearly.
Try alloc and initing the Attributedstring and then assign it to the self.lblTimeLeft.attributedText

Accessing data from C++ function into UITextView controller

I have a question guys.
I need to assign value to UITextView controller in my iPad app from a C++ function. So, the C++ function return a string and I can see that output in the output window. I merged C++ and XCode objective C by using .mm file. Now, I need to get the value from C++ function and add them in the UITextFiled.
Say for example.
My C++ function is like the following:
.cpp file
void *consumer (void* data)
{
SyncBuffThang<GLOBAL_BUFF_LEN,GLOBAL_BUFFS>* cc =(SyncBuffThang<GLOBAL_BUFF_LEN,GLOBAL_BUFFS>*) data;
affinity("consumer", cons);
for (int ii=0; ii<100; ii++)
{
unsigned char c = cc->get();
cc->res = c;
myVar = c;
cerr << "Consumer Get" << myVar << endl;
f +=c;
}
cerr << "Leaving consumer in method cons" << f << endl;
return 0;
}
int PC9::RunPC()
{
SyncBuffThang<GLOBAL_BUFF_LEN, GLOBAL_BUFFS> pc;
pthread_t p, c;
pthread_create(&p, 0, producer, &pc);
pthread_create(&c, 0, consumer, &pc);
pthread_join(p, 0);
pthread_join(c,0);
}
And the following sows the .mm file code.
#import "PC.h"
#import "PC9.h"
#import "GV.h"
#implementation PC
-(void)callFunctionPC
{
PC9 * myCPlusPlusObj; //A C++ object
myCPlusPlusObj=new PC9();
myCPlusPlusObj-> RunPC();
}
#end
See in the above function I can print the myVar but, I don't know how to access it or view it on UITextFile from my iPad app.
Friends I would really appreciate your help.
Thanks in advance.
-T
Here is the Answer I've been waiting for!!!
HelloWorld.h
#ifndef __Demo1__HelloWorld__
#define __Demo1__HelloWorld__
#include <iostream>
#endif
class HelloWorld {
public:
std::string Mtd_HelloWorld();
};
HelloWorld.cpp
#include "HelloWorld.h"
std::string HelloWorld::Mtd_HelloWorld()
{
std::string output;
output = "This is from C++";
return output;
}
HelloWorldM.h
#import <Foundation/Foundation.h>
#interface HelloWorld_M : NSObject
-(NSString *)CallCPP;
-(UIView *)CreateTextView:(NSString *)input;
#end
HelloWorld.mm
#import "PCViewController.h"
#include "HelloWorld.h"
#include "HelloWorld_M.h"
#implementation HelloWorld_M
-(NSString *)CallCPP
{
HelloWorld * myCPlusPlusObj; //A C++ object
myCPlusPlusObj=new HelloWorld();
std::string res = myCPlusPlusObj-> Mtd_HelloWorld();
NSString *result = [NSString stringWithCString:res.c_str()
encoding:[NSString defaultCStringEncoding]];
return result;
}
-(UIView *)CreateTextView:(NSString *)input
{
UITextView *myTextView = [[UITextView alloc] initWithFrame: CGRectMake (0.0,0.0,320.0,200.0)];
myTextView.text = input;
return myTextView;
}
#end
PCViewController.h
#import <UIKit/UIKit.h>
#import "HelloWorld_M.h"
#interface PCViewController : UIViewController
{
HelloWorld_M *objHelloWorld;
}
- (IBAction)RunHelloWorld:(id)sender;
#end
PCViewController.m
#import "PCViewController.h"
#interface PCViewController ()
#end
#implementation PCViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)RunHelloWorld:(id)sender
{
objHelloWorld = [[HelloWorld_M alloc]init];
[self.view addSubview: [objHelloWorld CreateTextView:[objHelloWorld CallCPP]]] ;
}
#end

UIAutomation testing with Cocos2d, possible?

Is it possible to use UIAutomation with cocos2d or any opengl application for that matter?
Specifically I want to use the zucchini framework to test my cocos2d game but that just uses UIAutomation anyway.
You can create custom steps in Zucchini and specify the coordinates to tap, e.g.
'Choose the red bird' : ->
target.tap({x:278, y:36})
'Press Fire' : ->
target.tap({x:170, y:260})
So, I got started with calabash-iOS and expanding on its backdoor. This is just for starters but with this you can get the accessibility label of the current CCScene, so you can check what screen is currently on and thus use for scripting actions. I'm not used to working with objc runtime, but as you can see it's possible to get properties, methods etc. A bit more digging and it should be possible to wrap more functionality, and hopefully something to wrap the cocos2d CCNode structure as well. This is a work in progress.
To use this you need to install https://github.com/calabash/calabash-ios and then implement the below function in the app delegate. Don't forget to set .accessibilityLabel to something like #"menu", #"game" or similar in your code. Optimally, only for the *-cal target, you don't want this code in production builds.
-(NSString*)calabashBackdoor:(NSString*)aIgnorable {
DLog(#"calabashBackdoor: %#", aIgnorable);
// UIApplication* app = [UIApplication sharedApplication];
if (aIgnorable != nil) {
NSArray* p = [aIgnorable componentsSeparatedByString:#" "];
NSString* command = [p objectAtIndex:0];
if ([command isEqualToString:#"getCurrentSceneLabel"]) {
CCDirector* director = [CCDirector sharedDirector];
DLog(#"director.runningScene.accessibilityLabel: %#", director.runningScene.accessibilityLabel);
return director.runningScene.accessibilityLabel;
}
else if ([command isEqualToString:#"class_copyMethodList"]) {
CCDirector* director = [CCDirector sharedDirector];
id inspectThisObject = director.runningScene;
DLog(#"inspectThisObject: %#, %#", [inspectThisObject class], inspectThisObject);
unsigned int count;
// To get the class methods of a class, use class_copyMethodList(object_getClass(cls), &count).
Method* methods = class_copyMethodList(object_getClass(inspectThisObject), &count);
//NSMutableString* returnstring = [NSMutableString string];
NSMutableArray* arrayOfMethodnames = [NSMutableArray array];
if (methods != NULL) {
for (int i = 0; i < count; i++) {
Method method = methods[i];
NSString* stringMethod = NSStringFromSelector(method_getName(method)); //NSStringFromSelector(method->method_name);
[arrayOfMethodnames addObject:stringMethod];
}
// An array of pointers of type Method describing the instance methods implemented by the class—any instance methods implemented by superclasses are not included. The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
free(methods);
}
DLog(#"arrayOfMethodnames: %#", arrayOfMethodnames);
return [arrayOfMethodnames componentsJoinedByString:#","];
}
else if ([command isEqualToString:#"class_copyPropertyList"]) {
CCDirector* director = [CCDirector sharedDirector];
id inspectThisObject = director.runningScene;
DLog(#"inspectThisObject: %#, %#", [inspectThisObject class], inspectThisObject);
unsigned int count;
// An array of pointers of type objc_property_t describing the properties declared by the class. Any properties declared by superclasses are not included. The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
//
// If cls declares no properties, or cls is Nil, returns NULL and *outCount is 0.
//
objc_property_t* properties = class_copyPropertyList(object_getClass(inspectThisObject), &count);
NSMutableArray* arrayOfProperties = [NSMutableArray array];
if (properties != NULL) {
for (int i = 0; i < count; i++) {
objc_property_t property = properties[i];
const char* CCS = property_getName(property);
NSString* str = [NSString stringWithUTF8String:CCS];
[arrayOfProperties addObject:str];
}
free(properties);
}
DLog(#"arrayOfProperties: %#", arrayOfProperties);
return [arrayOfProperties componentsJoinedByString:#","];
}
else {
DLog(#"Unhandled command: %#", command);
}
}
return #"calabashBackdoor nil!";
}
In Prefix.pch put this
#ifdef DEBUG
# define DLog( s, ... ) NSLog( #"<%p %#:(%d)> %#", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
# define DLog(...) /* */
#endif
#define ALog(...) NSLog(__VA_ARGS__)
When you get calabash-ios and running, add this in step_definitions/somesteps.rb:
Then(/^I backdoor (.+)$/) do |x|
backdoor("calabashBackdoor:", x)
end