axmol/samples/Javascript/CocosPlayer/Classes/iOS/ServerController.mm

676 lines
20 KiB
Plaintext

/*
* CocosBuilder: http://www.cocosbuilder.com
*
* Copyright (c) 2012 Zynga Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#import "ServerController.h"
#import "js_bindings_core.h"
#import "CCBReader.h"
#include "PlayerStatus.h"
#include "AppDelegate.h"
#import "CCBDirectoryComparer.h"
#import "SSZipArchive.h"
#import "RootViewController.h"
// Predefined messages
NSString *kCCBNetworkStatusStringWaiting = @"Waiting for connections";
NSString *kCCBNetworkStatusStringTooMany = @"Too many connections";
NSString *kCCBNetworkStatusStringConnected = @"Connected";
NSString *kCCBNetworkStatusStringShutDown = @"Server shut down";
NSString *kCCBPlayerStatusStringNotConnected = @"Connect by running CocosBuilder on the same local wireless network as CocosPlayer.\nIf multiple instances of CocosBuilder is run on the same network, use a unique pairing code.";
NSString *kCCBPlayerStatusStringIdle = @"Idle";
NSString *kCCBPlayerStatusStringUnzip = @"Action: Unzip game";
NSString *kCCBPlayerStatusStringStop = @"Action: Stop";
NSString *kCCBPlayerStatusStringPlay = @"Action: Run";
NSString *kCCBPlayerStatusStringScript = @"Action: Executing script";
@implementation ServerController
@synthesize networkStatus, playerStatus, playerWindowDisplayed, isRetina, isIPhone;
#pragma mark Initializers and setup
- (NSString*) protocolIdentifier
{
NSString* pairing = [[NSUserDefaults standardUserDefaults] objectForKey:@"pairing"];
updatePairingLabel([pairing cStringUsingEncoding:NSASCIIStringEncoding]);
if (pairing)
{
return [NSString stringWithFormat:@"CocosP-%@",pairing];
}
else
{
return @"CocosPlayer";
}
}
- (id) init
{
self = [super init];
if (!self) return NULL;
connectedClients = [[NSMutableSet alloc] init];
server = [[ThoMoServerStub alloc] initWithProtocolIdentifier:[self protocolIdentifier]];
[server setDelegate:self];
networkStatus = kCCBNetworkUninitialized;
playerStatus = kCCBPlayerStatuskUninitialized;
playerWindowDisplayed = YES;
return self;
}
#pragma mark Redirection of std out
- (void) redirectStdErr
{
NSPipe* pipe = [NSPipe pipe];
pipeReadHandle = [pipe fileHandleForReading];
[pipeReadHandle readInBackgroundAndNotify];
int err = dup2([[pipe fileHandleForWriting] fileDescriptor], STDERR_FILENO);
if (!err) NSLog(@"ConsoleWindow: Failed to redirect stderr");
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(readData:) name:NSFileHandleReadCompletionNotification object:pipeReadHandle];
[pipeReadHandle retain];
}
- (void) readData:(NSNotification*)notification
{
[pipeReadHandle readInBackgroundAndNotify] ;
NSString *str = [[NSString alloc] initWithData: [[notification userInfo] objectForKey: NSFileHandleNotificationDataItem] encoding: NSASCIIStringEncoding] ;
[self sendLog:str];
}
#pragma mark Control methods
- (void) start
{
if (server)
{
[server start];
// Redirect std out
[self redirectStdErr];
}
}
- (void) startIfNotStarted
{
if (!server)
{
server = [[ThoMoServerStub alloc] initWithProtocolIdentifier:[self protocolIdentifier]];
[server setDelegate:self];
[server start];
self.networkStatus = kCCBNetworkStatusWaiting;
}
}
- (void) stop
{
if (server)
{
self.networkStatus = kCCBNetworkStatusShutDown;
//[[[PlayerStatusLayer sharedInstance] lblInstructions] setString:kCCBPlayerStatusStringStop];
handle_ccb_stop();
[server stop];
[server release];
server = NULL;
[connectedClients removeAllObjects];
}
}
- (void) updatePairing
{
// Stop old server
[server stop];
[server release];
server = NULL;
[connectedClients removeAllObjects];
// Start new server
server = [[ThoMoServerStub alloc] initWithProtocolIdentifier:[self protocolIdentifier]];
[server setDelegate:self];
[server start];
self.networkStatus = kCCBNetworkStatusWaiting;
}
- (void) pressedPair
{
UIAlertView* alert = [[[UIAlertView alloc] initWithTitle:@"Pair Device" message:@"Enter a 4 digit pairing number (use the same number in CocosBuilder)" delegate:self cancelButtonTitle:@"Remove" otherButtonTitles:@"Set Pairing", nil] autorelease];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
UITextField* textField = [alert textFieldAtIndex:0];
textField.keyboardType = UIKeyboardTypeNumberPad;
textField.delegate = self;
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString* pairing = NULL;
if (buttonIndex == 1)
{
UITextField* textField = [alertView textFieldAtIndex:0];
pairing = textField.text;
if ([pairing isEqualToString:@""]) pairing = NULL;
}
if (pairing)
{
[[NSUserDefaults standardUserDefaults] setObject:pairing forKey:@"pairing"];
}
else
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pairing"];
}
[[NSUserDefaults standardUserDefaults] synchronize];
updatePairing([pairing cStringUsingEncoding:NSASCIIStringEncoding]);
updatePairingLabel([pairing cStringUsingEncoding:NSASCIIStringEncoding]);
}
- (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// Validate string length
NSUInteger newLength = [theTextField.text length] + [string length] - range.length;
if (newLength > 4) return NO;
// Make sure it only uses numbers
NSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];
for (int i = 0; i < [string length]; i++) {
unichar c = [string characterAtIndex:i];
if (![myCharSet characterIsMember:c]) {
return NO;
}
}
return YES;
}
#pragma mark Helper methods
- (void) executeJavaScript:(NSString*)script
{
self.playerStatus = kCCBPlayerStatusExecuteScript;
jsval out;
bool success = handle_eval_script([script cStringUsingEncoding:NSUTF8StringEncoding], &out);
//BOOL success = [[JSBCore sharedInstance] evalString:script outVal:&out];
// NSThread *cocos2dThread = [[CCDirector sharedDirector] runningThread];
//
// [cocos2dThread performBlock:^(void) {
// NSString * string = @"None\n";
// jsval out;
// BOOL success = [[JSBCore sharedInstance] evalString:script outVal:&out];
//
// if(success)
// {
// /*
// if(JSVAL_IS_BOOLEAN(out))
// {
// string = [NSString stringWithFormat:@"Result(bool): %@.\n", (JSVAL_TO_BOOLEAN(out)) ? @"true" : @"false"];
// }
// else if(JSVAL_IS_INT(out))
// {
// string = [NSString stringWithFormat:@"Result(int): %i.\n", JSVAL_TO_INT(out)];
// }
// else if(JSVAL_IS_DOUBLE(out))
// {
// string = [NSString stringWithFormat:@"Result(double): %d.\n", JSVAL_TO_DOUBLE(out)];
// }
// else if(JSVAL_IS_STRING(out)) {
// NSString *tmp;
// jsval_to_nsstring( [[ScriptingCore sharedInstance] globalContext], out, &tmp );
// string = [NSString stringWithFormat:@"Result(string): %d.\n", tmp];
// }
// else if (JSVAL_IS_VOID(out) )
// string = @"Result(void):\n";
// else if (JSVAL_IS_OBJECT(out) )
// string = @"Result(object):\n";
// */
// string = @"Success\n";
// }
// else
// {
// string = [NSString stringWithFormat:@"Error evaluating script:\n#############################\n%@\n#############################\n", script];
// }
//
// //[self sendResultString:string];
// }
// waitUntilDone:NO];
}
- (void) listDirectory:(NSString*)dir prefix:(NSString*)prefix
{
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:NULL];
for (NSString* file in files)
{
NSLog(@"%@%@", prefix, file);
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:[dir stringByAppendingPathComponent:file] isDirectory:&isDir] && isDir)
{
[self listDirectory:[dir stringByAppendingPathComponent:file] prefix:[prefix stringByAppendingString:@" "]];
}
}
}
- (void) extractZipData:(NSData*)data
{
self.playerStatus = kCCBPlayerStatusUnzip;
id runBlock = ^(void) {
std::string path = cocos2d::CCFileUtils::sharedFileUtils()->getWritablePath();
NSString *writeablePath = [NSString stringWithCString:path.c_str() encoding:NSASCIIStringEncoding];
// NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString* dirPath = [writeablePath stringByAppendingPathComponent:@"ccb"];
NSString* zipPath = [dirPath stringByAppendingPathComponent:@"ccb.zip"];
BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:dirPath isDirectory:&isDirectory])
{
[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:NULL error:NULL];
}
if(![data writeToFile:zipPath atomically:YES])
{
NSLog(@"CocosPlayer: Failed to write zip file");
return;
}
if(![SSZipArchive unzipFileAtPath:zipPath toDestination:dirPath overwrite:YES password:NULL error:NULL]) {
NSLog(@"CocosPlayer: Failed to unzip resources");
}
[self listDirectory:dirPath prefix:@""];
// Send updated list of files on device
[self sendFileList];
};
double delayInSeconds = 0.01;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), runBlock);
}
-(void) stopJSApp
{
if( ! playerWindowDisplayed ) {
//[[AppController appController] stopJSApp];
playerWindowDisplayed = YES;
NSUInteger orientations = 0;
orientations |= UIInterfaceOrientationMaskPortrait;
[RootViewController setDeviceOrientation:orientations];
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
handle_set_orient(true);
}
}
-(void) runJSApp
{
[self stopMain];
handle_ccb_run();
playerWindowDisplayed = NO;
}
- (void) stopMain
{
if( ! playerWindowDisplayed ) {
self.playerStatus = kCCBPlayerStatusStop;
[self stopJSApp];
handle_ccb_stop();
CCBNetworkStatus tmp = networkStatus;
networkStatus = kCCBNetworkUninitialized;
self.networkStatus = tmp;
}
}
- (void) runMain
{
double delayInSeconds = 0.05;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
self.playerStatus = kCCBPlayerStatusPlay;
[self runJSApp];
});
}
#pragma mark Server callbacks
- (void) server:(ThoMoServerStub *)theServer acceptedConnectionFromClient:(NSString *)aClientIdString
{
[connectedClients addObject:aClientIdString];
if (connectedClients.count == 1)
{
self.networkStatus = kCCBNetworkStatusConnected;
[self sendDeviceName];
[self sendFileList];
}
else
{
self.networkStatus = kCCBNetworkStatusTooMany;
}
}
- (void)server:(ThoMoServerStub *)theServer lostConnectionToClient:(NSString *)aClientIdString errorMessage:(NSString *)errorMessage
{
[connectedClients removeObject:aClientIdString];
if (connectedClients.count == 0)
{
self.networkStatus = kCCBNetworkStatusWaiting;
}
else if (connectedClients.count == 1)
{
self.networkStatus = kCCBNetworkStatusConnected;
}
else
{
self.networkStatus = kCCBNetworkStatusTooMany;
}
}
- (void)serverDidShutDown:(ThoMoServerStub *)theServer
{
self.networkStatus = kCCBNetworkStatusWaiting;
if( server == theServer || !server) {
[server release];
server = NULL;
[connectedClients removeAllObjects];
[self startIfNotStarted];
}
}
- (void)netServiceProblemEncountered:(NSString *)errorMessage onServer:(ThoMoServerStub *)theServer
{
if( server == theServer ) {
[server stop];
[server release];
server = NULL;
[connectedClients removeAllObjects];
}
}
- (void) server:(ThoMoServerStub *)theServer didReceiveData:(id)theData fromClient:(NSString *)aClientIdString
{
if (connectedClients.count != 1) return;
NSDictionary* msg = theData;
NSString* cmd = [msg objectForKey:@"cmd"];
if ([cmd isEqualToString:@"script"])
{
NSString* script = [msg objectForKey:@"script"];
[self executeJavaScript:script];
}
else if ([cmd isEqualToString:@"run"])
{
[self runMain];
}
else if ([cmd isEqualToString:@"stop"])
{
[self stopMain];
}
else if ([cmd isEqualToString:@"zip"])
{
NSData* zipData = [msg objectForKey:@"data"];
// Extract zip file received from CocosBuilder
[self extractZipData:zipData];
}
else if ([cmd isEqualToString:@"settings"])
{
NSArray* arr = [msg objectForKey:@"orientations"];
NSUInteger orientations = 0;
if ([[arr objectAtIndex:0] boolValue]) orientations |= UIInterfaceOrientationMaskPortrait;
if ([[arr objectAtIndex:1] boolValue]) orientations |= UIInterfaceOrientationMaskPortraitUpsideDown;
if ([[arr objectAtIndex:2] boolValue]) orientations |= UIInterfaceOrientationMaskLandscapeLeft;
if ([[arr objectAtIndex:3] boolValue]) orientations |= UIInterfaceOrientationMaskLandscapeRight;
[RootViewController setDeviceOrientation:orientations];
if([[arr objectAtIndex:2] boolValue] || [[arr objectAtIndex:3] boolValue]) {
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeLeft];
} else {
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
}
handle_set_orient(([[arr objectAtIndex:0] boolValue] || [[arr objectAtIndex:1] boolValue]) ? true: false);
}
}
#pragma mark Server/Player status
-(void) setNetworkStatus:(CCBNetworkStatus)aNetworkStatus
{
if( networkStatus != aNetworkStatus ) {
networkStatus = aNetworkStatus;
//PlayerStatusLayer *statusLayer = [PlayerStatusLayer sharedInstance];
switch (networkStatus) {
case kCCBNetworkStatusConnected:
if( playerWindowDisplayed)
handle_set_status((const char *)[kCCBNetworkStatusStringConnected cStringUsingEncoding:NSASCIIStringEncoding]);
self.playerStatus = kCCBPlayerStatusIdle;
break;
case kCCBNetworkStatusShutDown:
if( playerWindowDisplayed)
handle_set_status((const char *)[kCCBNetworkStatusStringShutDown cStringUsingEncoding:NSASCIIStringEncoding]);
self.playerStatus = kCCBPlayerStatusNotConnected;
break;
case kCCBNetworkStatusTooMany:
if( playerWindowDisplayed)
handle_set_status((const char *)[kCCBNetworkStatusStringWaiting cStringUsingEncoding:NSASCIIStringEncoding]);
[self stopJSApp];
self.playerStatus = kCCBPlayerStatusNotConnected;
break;
case kCCBNetworkStatusWaiting:
if( playerWindowDisplayed)
handle_set_status((const char *)[kCCBNetworkStatusStringWaiting cStringUsingEncoding:NSASCIIStringEncoding]);
self.playerStatus = kCCBPlayerStatusNotConnected;
[self stopJSApp];
break;
default:
break;
}
}
}
-(void) setPlayerStatus:(CCBPlayerStatus)aPlayerStatus
{
if( playerStatus != aPlayerStatus) {
playerStatus = aPlayerStatus;
//PlayerStatusLayer *statusLayer = [PlayerStatusLayer sharedInstance];
switch (playerStatus) {
case kCCBPlayerStatusExecuteScript:
if( playerWindowDisplayed) {
handle_set_message((const char *)[kCCBPlayerStatusStringScript cStringUsingEncoding:NSASCIIStringEncoding]);
}
break;
case kCCBPlayerStatusPlay:
if( playerWindowDisplayed)
handle_set_message((const char *)[kCCBPlayerStatusStringPlay cStringUsingEncoding:NSASCIIStringEncoding]);
break;
case kCCBPlayerStatusStop:
if( playerWindowDisplayed)
handle_set_message((const char *)[kCCBPlayerStatusStringStop cStringUsingEncoding:NSASCIIStringEncoding]);
break;
case kCCBPlayerStatusUnzip:
if( playerWindowDisplayed)
handle_set_message((const char *)[kCCBPlayerStatusStringUnzip cStringUsingEncoding:NSASCIIStringEncoding]);
break;
case kCCBPlayerStatusIdle:
if( playerWindowDisplayed)
handle_set_message((const char *)[kCCBPlayerStatusStringIdle cStringUsingEncoding:NSASCIIStringEncoding]);
break;
case kCCBPlayerStatusNotConnected:
if( playerWindowDisplayed)
handle_set_message((const char *)[kCCBPlayerStatusStringNotConnected cStringUsingEncoding:NSASCIIStringEncoding]);
break;
default:
break;
}
}
}
#pragma mark Sending messages
- (void) sendMessage:(NSDictionary*) msg
{
if (connectedClients.count == 1)
{
[server sendToAllClients:msg];
}
}
- (NSString*) getUUID
{
// Check user defaults
NSString* uuid = [[NSUserDefaults standardUserDefaults] objectForKey:@"uuid"];
if (uuid) return uuid;
// Generate UUID
CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault);
uuid = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidObject);
CFRelease(uuidObject);
// Store generated id
[[NSUserDefaults standardUserDefaults] setObject:uuid forKey:@"uuid"];
return uuid;
}
- (void) sendDeviceName
{
NSMutableDictionary* msg = [NSMutableDictionary dictionary];
[msg setObject:@"deviceinfo" forKey:@"cmd"];
[msg setObject:[[UIDevice currentDevice] name] forKey:@"devicename"];
//[msg setObject:[AppController appController].deviceType forKey:@"devicetype"];
if(self.isIPhone)
{
[msg setObject:@"iPhone" forKey:@"devicetype"];
}
else
{
[msg setObject:@"iPad" forKey:@"devicetype"];
}
[msg setObject:[NSNumber numberWithBool:self.isRetina] forKey:@"retinadisplay"];
[msg setObject:[self getUUID] forKey:@"uuid"];
[self sendMessage:msg];
}
- (void) sendResultString:(NSString*) str
{
NSMutableDictionary* msg = [NSMutableDictionary dictionary];
[msg setObject:@"result" forKey:@"cmd"];
[msg setObject:str forKey:@"result"];
[self sendMessage:msg];
}
- (void) sendLog:(NSString*)log
{
NSMutableDictionary* msg = [NSMutableDictionary dictionary];
[msg setObject:@"log" forKey:@"cmd"];
[msg setObject:log forKey:@"string"];
[self sendMessage:msg];
}
- (void) sendFileList
{
// Get the contents of the CCB working directory, with files
CCBDirectoryComparer* dc = [[[CCBDirectoryComparer alloc] init] autorelease];
std::string path = cocos2d::CCFileUtils::sharedFileUtils()->getWritablePath();
NSString *writeablePath = [NSString stringWithCString:path.c_str() encoding:NSASCIIStringEncoding];
// NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString* dirPath = [writeablePath stringByAppendingPathComponent:@"ccb"];
// NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
// NSString* dirPath = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:@"ccb"];
[dc loadDirectory:dirPath];
NSDictionary* dirFiles = dc.files;
// Send message
NSMutableDictionary* msg = [NSMutableDictionary dictionary];
[msg setObject:@"filelist" forKey:@"cmd"];
[msg setObject:dirFiles forKey:@"filelist"];
[self sendMessage:msg];
for (id key in msg) {
NSLog(@"key: %@, value: %@", key, [msg objectForKey:key]);
}
}
#pragma mark Common
- (void) dealloc
{
[server release];
[connectedClients release];
[super dealloc];
}
@end