diff --git a/game/sdl/mac/machttprequest.h b/game/sdl/mac/machttprequest.h index ee44759..4021803 100644 --- a/game/sdl/mac/machttprequest.h +++ b/game/sdl/mac/machttprequest.h @@ -9,22 +9,6 @@ #include "base/thread.h" #include "base/bytebuffer.h" -namespace wi { -class MacHttpRequest; -} - -@interface ConnectionDelegate : NSObject { - NSURLConnection *conn_; - wi::MacHttpRequest *req_; -} -- (void)submit; -- (void)cancel; -- (void)connection:(NSURLConnection *)conn - didFailWithError:(NSError *)error; -- (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data; -- (void)connectionDidFinishLoading:(NSURLConnection *)conn; -@end - namespace wi { class MacHttpRequest : public HttpRequest, base::MessageHandler { @@ -32,8 +16,7 @@ public: MacHttpRequest(HttpResponseHandler *phandler); ~MacHttpRequest(); - void Submit(); - void Release(); + void Dispose(); NSURLRequest *CreateNSURLRequest(); void OnReceivedResponse(NSHTTPURLResponse *resp); void OnReceivedData(NSData *data); @@ -41,8 +24,22 @@ public: void OnError(NSError *error); private: + virtual void OnMessage(base::Message *pmsg); + HttpResponseHandler *handler_; - ConnectionDelegate *delegate_; +}; + +struct ReceivedResponseParams : base::MessageData { + int code; + Map headers; +}; + +struct ReceivedDataParams : base::MessageData { + base::ByteBuffer bb; +}; + +struct ErrorParams : base::MessageData { + char szError[80]; }; } // namespace wi diff --git a/game/sdl/mac/machttprequest.mm b/game/sdl/mac/machttprequest.mm index 0928c2a..496ffd6 100644 --- a/game/sdl/mac/machttprequest.mm +++ b/game/sdl/mac/machttprequest.mm @@ -1,148 +1,151 @@ #include "game/sdl/mac/machttprequest.h" +#include "game/sdl/sysmessages.h" #include "base/thread.h" -@implementation ConnectionDelegate +// Requests come in from the game thread; NS* api calls occur on main +// thread. -- (id)initWithRequest:(wi::MacHttpRequest *)req { - self = [super init]; - if (self != nil) { - req_ = req; - conn_ = nil; - } - return self; -} - -- (void)submit { - NSURLRequest *req = req_->CreateNSURLRequest(); - conn_ = [NSURLConnection - connectionWithRequest:req - delegate:self]; -} - -- (void)cancel { - [conn_ cancel]; - conn_ = nil; -} - -- (void)connection:(NSURLConnection *)conn - didReceiveResponse:(NSURLResponse *)resp { - req_->OnReceivedResponse((NSHTTPURLResponse *)resp); -} - -- (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data { - req_->OnReceivedData(data); -} - -- (void)connectionDidFinishLoading:(NSURLConnection *)conn { - req_->OnFinishedLoading(); -} - -- (void)connection:(NSURLConnection *)conn - didFailWithError:(NSError *)error { - NSLog(@"error: %@", error); - req_->OnError(error); -} -@end - -// C++ implementation of HttpRequest interface for Mac +// C++ implementation of HttpRequest interface for iPhone namespace wi { -MacHttpRequest::MacHttpRequest(HttpResponseHandler *handler) : - handler_(handler), delegate_(nil) { +MacHttpRequest::MacHttpRequest(HttpResponseHandler *handler) : handler_(handler) { } MacHttpRequest::~MacHttpRequest() { } -void MacHttpRequest::Submit() { - delegate_ = [[ConnectionDelegate alloc] initWithRequest:this]; - [delegate_ submit]; -} - -void MacHttpRequest::Release() { - [delegate_ cancel]; - delegate_ = nil; - Dispose(); +void MacHttpRequest::Dispose() { + // Called on game thread + thread_.Clear(this); + MessageHandler::Dispose(); } NSURLRequest *MacHttpRequest::CreateNSURLRequest() { - @autoreleasepool { - NSMutableURLRequest *req = [[NSMutableURLRequest alloc] init]; + // Called on main thread + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSMutableURLRequest *req = [[NSMutableURLRequest alloc] init]; - // Set the url - NSString *s = [NSString stringWithCString:url_.c_str() - encoding:[NSString defaultCStringEncoding]]; - [req setURL:[NSURL URLWithString:s]]; + // Set the url + NSString *s = [NSString stringWithCString:url_.c_str() + encoding:[NSString defaultCStringEncoding]]; + [req setURL:[NSURL URLWithString:s]]; - // Set the method - s = [NSString stringWithCString:method_.c_str() - encoding:[NSString defaultCStringEncoding]]; - [req setHTTPMethod:s]; + // Set the method + s = [NSString stringWithCString:method_.c_str() + encoding:[NSString defaultCStringEncoding]]; + [req setHTTPMethod:s]; - // Set the body - if (pbb_ != NULL) { - int cb; - void *pv = pbb_->Strip(&cb); - NSData *data = [NSData dataWithBytesNoCopy:(void *)pv length:cb]; - [req setHTTPBody:data]; + // Set the body + if (pbb_ != NULL) { + int cb; + void *pv = pbb_->Strip(&cb); + NSData *data = [NSData dataWithBytesNoCopy:(void *)pv length:cb]; + [req setHTTPBody:data]; + } + + // Set timeout + [req setTimeoutInterval:timeout_]; + + // Set cache policy + [req setCachePolicy:NSURLRequestReloadIgnoringCacheData]; + + // Set headers + Enum enm; + char szKey[128]; + while (headers_.EnumKeys(&enm, szKey, sizeof(szKey))) { + char szValue[256]; + if (headers_.GetValue(szKey, szValue, sizeof(szValue))) { + NSString *key = [NSString stringWithCString:szKey + encoding:[NSString defaultCStringEncoding]]; + NSString *value = [NSString stringWithCString:szValue + encoding:[NSString defaultCStringEncoding]]; + [req setValue:value forHTTPHeaderField:key]; } + } - // Set timeout - [req setTimeoutInterval:timeout_]; + // Done + [pool release]; + return req; +} - // Set cache policy - [req setCachePolicy:NSURLRequestReloadIgnoringCacheData]; - - // Set headers - Enum enm; - char szKey[128]; - while (headers_.EnumKeys(&enm, szKey, sizeof(szKey))) { - char szValue[256]; - if (headers_.GetValue(szKey, szValue, sizeof(szValue))) { - NSString *key = [NSString stringWithCString:szKey - encoding:[NSString defaultCStringEncoding]]; - NSString *value = [NSString stringWithCString:szValue - encoding:[NSString defaultCStringEncoding]]; - [req setValue:value forHTTPHeaderField:key]; - } +void MacHttpRequest::OnMessage(base::Message *pmsg) { + switch (pmsg->id) { + case kidmReceivedResponse: + { + ReceivedResponseParams *pparams = + (ReceivedResponseParams *)pmsg->data; + handler_->OnReceivedResponse(this, pparams->code, + &pparams->headers); + delete pparams; } + break; - // Done - return req; + case kidmReceivedData: + { + ReceivedDataParams *pparams = + (ReceivedDataParams *)pmsg->data; + handler_->OnReceivedData(this, &pparams->bb); + delete pparams; + } + break; + + case kidmFinishedLoading: + handler_->OnFinishedLoading(this); + break; + + case kidmError: + { + ErrorParams *pparams = (ErrorParams *)pmsg->data; + handler_->OnError(this, pparams->szError); + delete pparams; + } + break; } } void MacHttpRequest::OnReceivedResponse(NSHTTPURLResponse *resp) { - Map headers; + // Called on main thread. Populate ReceivedResponseParams + ReceivedResponseParams *pparams = new ReceivedResponseParams; NSDictionary *dict = [resp allHeaderFields]; for (NSString *k in dict) { NSString *v = [dict objectForKey:k]; - headers.SetValue( + pparams->headers.SetValue( [k cStringUsingEncoding:[NSString defaultCStringEncoding]], [v cStringUsingEncoding:[NSString defaultCStringEncoding]]); } - int code = [resp statusCode]; - handler_->OnReceivedResponse(this, code, &headers); + pparams->code = (int)[resp statusCode]; + + // Post this to the game thread + thread_.Post(kidmReceivedResponse, this, pparams); } void MacHttpRequest::OnReceivedData(NSData *data) { - base::ByteBuffer bb; - bb.WriteBytes((const byte *)[data bytes], [data length]); - handler_->OnReceivedData(this, &bb); + // Called on main thread. Populate ReceivedDataParams + ReceivedDataParams *pparams = new ReceivedDataParams; + pparams->bb.WriteBytes((const byte *)[data bytes], (int)[data length]); + + // Post this to the game thread + thread_.Post(kidmReceivedData, this, pparams); } void MacHttpRequest::OnFinishedLoading() { - handler_->OnFinishedLoading(this); + // Called on main thread. Post this to game thread. + thread_.Post(kidmFinishedLoading, this); } void MacHttpRequest::OnError(NSError *error) { + // Called on main thread. Populate ErrorParams. Use + // localizedDescription. Note there is also localizedFailureReason; + // not sure which is better at the moment const char *psz = [[error localizedDescription] cStringUsingEncoding: [NSString defaultCStringEncoding]]; - char szError[80]; - strncpyz(szError, psz, sizeof(szError)); - handler_->OnError(this, szError); + ErrorParams *pparams = new ErrorParams; + strncpyz(pparams->szError, psz, sizeof(pparams->szError)); + + // Called on main thread. Post this to game thread. + thread_.Post(kidmError, this, pparams); } } // namespace wi diff --git a/game/sdl/mac/machttpservice.h b/game/sdl/mac/machttpservice.h index 20e5287..c826972 100644 --- a/game/sdl/mac/machttpservice.h +++ b/game/sdl/mac/machttpservice.h @@ -8,10 +8,15 @@ namespace wi { class MacHttpService : public HttpService { public: + MacHttpService(); + // HttpService methods virtual HttpRequest *NewRequest(HttpResponseHandler *phandler); virtual void SubmitRequest(HttpRequest *preq); virtual void ReleaseRequest(HttpRequest *preq); + +private: + void *delegate_; }; } // namespace wi diff --git a/game/sdl/mac/machttpservice.mm b/game/sdl/mac/machttpservice.mm index bc56a9b..ac61713 100644 --- a/game/sdl/mac/machttpservice.mm +++ b/game/sdl/mac/machttpservice.mm @@ -1,20 +1,156 @@ +#import +#import + #include "game/sdl/mac/machttpservice.h" #include "game/sdl/mac/machttprequest.h" +#include "game/sdl/sysmessages.h" +#include "game/sdl/hosthelpers.h" +#include + +typedef std::map TaskMap; + +// HttpService calls come in on the game thread. In order to use +// iPhone NS* Http apis, requests execute on the main thread. + +@interface MacHttpRequestWrapper : NSObject { + wi::MacHttpRequest *req_; +} +@end + +@implementation MacHttpRequestWrapper +- (id)initWithRequest:(wi::MacHttpRequest *)req { + self = [super init]; + if (self != nil) { + req_ = req; + } + return self; +} + +- (wi::MacHttpRequest *)request { + return req_; +} +@end + +@interface SessionDelegate : NSObject { + NSURLSession *session_; + TaskMap taskmap_; +} +@end + +@implementation SessionDelegate +- (id)init { + self = [super init]; + if (self != nil) { + session_ = nil; + } + return self; +} + +- (void)dealloc { + [session_ release]; + [super dealloc]; +} + +- (void)cancel:(MacHttpRequestWrapper *)wrapper { + wi::MacHttpRequest *req = [wrapper request]; + NSURLSessionDataTask *task = nil; + TaskMap::iterator it = taskmap_.begin(); + for (; it != taskmap_.end(); it++) { + if (it->second == req) { + task = it->first; + taskmap_.erase(it); + break; + } + } + if (task != nil) { + [task cancel]; + [task release]; + } + [wrapper release]; +} + +- (void)submit:(MacHttpRequestWrapper *)wrapper { + if (session_ == nil) { + session_ = [NSURLSession + sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + } + wi::MacHttpRequest *req = [wrapper request]; + NSURLRequest *url_req = req->CreateNSURLRequest(); + NSURLSessionDataTask *task = [session_ dataTaskWithRequest:url_req]; + [url_req release]; + [task retain]; + taskmap_.insert(TaskMap::value_type(task, req)); + [task resume]; + [wrapper release]; +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task + didReceiveResponse:(NSURLResponse *)resp + completionHandler:(void (^)(NSURLSessionResponseDisposition disp))handler { + TaskMap::iterator it = taskmap_.find(task); + if (it != taskmap_.end()) { + it->second->OnReceivedResponse((NSHTTPURLResponse *)resp); + } + handler(NSURLSessionResponseAllow); +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task + didReceiveData:(NSData *)data { + TaskMap::iterator it = taskmap_.find(task); + if (it != taskmap_.end()) { + it->second->OnReceivedData(data); + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task + didCompleteWithError:(NSError *)error { + TaskMap::iterator it = taskmap_.find((NSURLSessionDataTask *)task); + if (it != taskmap_.end()) { + if (error == nil) { + it->second->OnFinishedLoading(); + } else { + NSLog(@"error: %@", error); + it->second->OnError(error); + } + } +} +@end namespace wi { +MacHttpService::MacHttpService() { + delegate_ = NULL; +} + HttpRequest *MacHttpService::NewRequest(HttpResponseHandler *phandler) { return new MacHttpRequest(phandler); } void MacHttpService::SubmitRequest(HttpRequest *preq) { - MacHttpRequest *preqT = (MacHttpRequest *)preq; - preqT->Submit(); + if (delegate_ == NULL) { + delegate_ = [[SessionDelegate alloc] init]; + } + MacHttpRequestWrapper *wrapper = [[MacHttpRequestWrapper alloc] + initWithRequest:(MacHttpRequest *)preq]; + SessionDelegate *delegate = (SessionDelegate *)delegate_; + [delegate performSelectorOnMainThread:@selector(submit:) + withObject:wrapper waitUntilDone: NO]; } void MacHttpService::ReleaseRequest(HttpRequest *preq) { - MacHttpRequest *preqT = (MacHttpRequest *)preq; - preqT->Release(); + // This can cause a deadlock when exiting because of how the main thread + // is synchronizing with the game thread to exit before it does + MacHttpRequest *req = (MacHttpRequest *)preq; + if (!HostHelpers::IsExiting()) { + MacHttpRequestWrapper *wrapper = [[MacHttpRequestWrapper alloc] + initWithRequest:req]; + SessionDelegate *delegate = (SessionDelegate *)delegate_; + [delegate performSelectorOnMainThread:@selector(cancel:) + withObject:wrapper waitUntilDone: YES]; + } + req->Dispose(); } } // namespace wi