This commit is contained in:
watrabi
2025-09-18 17:55:52 -04:00
commit 977f1ff4b8
15030 changed files with 17324420 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
//
// CMCrashReporter.h
// CMCrashReporter-App
//
// Created by Jelle De Laender on 20/01/08.
// Copyright 2008 CodingMammoth. All rights reserved.
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
//
// Current version: 1.1 (September 2010)
//
#import <Cocoa/Cocoa.h>
#import "CMCrashReporterGlobal.h"
@interface CMCrashReporter : NSObject {
}
+ (void)check;
+ (NSArray *)getReports;
@end
+85
View File
@@ -0,0 +1,85 @@
//
// CMCrashReporter.m
// CMCrashReporter-App
//
// Created by Jelle De Laender on 20/01/08.
// Copyright 2008 CodingMammoth. All rights reserved.
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
//
// Modified by ROBLOX 2011
#import "CMCrashReporter.h"
@implementation CMCrashReporter
+ (void) submitFile:(NSString *)file
{
NSMutableData* regData = [[NSMutableData alloc] initWithCapacity:100];
NSString* s = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
[regData appendData:[s dataUsingEncoding:NSASCIIStringEncoding]];
NSURL *url = [NSURL URLWithString:[CMCrashReporterGlobal crashReportURL]];
NSMutableURLRequest* post = [NSMutableURLRequest requestWithURL:url];
[post setHTTPMethod: @"POST"];
[post setHTTPBody:regData];
NSURLResponse* response;
NSError* error;
#warning TODO: Async
NSData* result = [NSURLConnection sendSynchronousRequest:post returningResponse:&response error:&error];
NSString *res = [[[NSString alloc] initWithData:result encoding:NSASCIIStringEncoding] autorelease];
NSString *compare = [res stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
BOOL success = ([compare isEqualToString:@"ok"]);
if (success)
if ([[NSFileManager defaultManager] fileExistsAtPath:file])
{
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:file error:&error];
}
}
+(void)check
{
#warning TODO: Turn on CMCrashReporter
#if 0
NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults];
if ([CMCrashReporterGlobal checkOnCrashes] && ![defaults boolForKey:@"CMCrashReporterIgnoreCrashes"]) {
NSArray *reports = [CMCrashReporter getReports];
if ([reports count] > 0) {
int max = MIN([CMCrashReporterGlobal numberOfMaximumReports],[reports count]);
if (max == 0) max = [reports count];
for (int i = 0; i < max; i++)
[CMCrashReporter submitFile:[reports objectAtIndex:i]];
}
}
#endif
}
+(NSArray *)getReports
{
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([CMCrashReporterGlobal isRunningLeopard]) {
// (Snow) Leopard format is AppName_Year_Month_Day
NSString *file;
NSString *path = [@"~/Library/Logs/CrashReporter/" stringByExpandingTildeInPath];
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
NSMutableArray *array = [NSMutableArray array];
while (file = [dirEnum nextObject])
if ([file hasPrefix:[CMCrashReporterGlobal appName]])
[array addObject:[[NSString stringWithFormat:@"~/Library/Logs/CrashReporter/%@",file] stringByExpandingTildeInPath]];
return array;
} else {
// Tiger Formet is AppName.crash.log
NSString *path = [[NSString stringWithFormat:@"~/Library/Logs/CrashReporter/%@.crash.log",[CMCrashReporterGlobal appName]] stringByExpandingTildeInPath];
if ([fileManager fileExistsAtPath:path]) return [NSArray arrayWithObject:path];
else return nil;
}
}
@end
+80
View File
@@ -0,0 +1,80 @@
//
// CMCrashReporter.m
// CMCrashReporter-App
//
// Created by Jelle De Laender on 20/01/08.
// Copyright 2008 CodingMammoth. All rights reserved.
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
//
// Modified by ROBLOX 2011
void uploadAndDeletFileAsync(const char* url, const char* file);
#import "CMCrashReporter.h"
@implementation CMCrashReporter
+ (void) submitFile:(NSString *)file
{
NSLog(@"submitFile: %@", file);
if ([[NSFileManager defaultManager] fileExistsAtPath:file] == NO)
return;
NSString* base = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"RbxBaseUrl"];
NSString* crashURL = [base stringByAppendingString:@"error/dmp.ashx"];
uploadAndDeletFileAsync(
[crashURL cStringUsingEncoding:NSUTF8StringEncoding],
[file cStringUsingEncoding:NSUTF8StringEncoding]
);
}
+(void)check
{
NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults];
if ([CMCrashReporterGlobal checkOnCrashes] && ![defaults boolForKey:@"CMCrashReporterIgnoreCrashes"]) {
NSArray *reports = [CMCrashReporter getReports];
if ([reports count] > 0) {
int max = MIN([CMCrashReporterGlobal numberOfMaximumReports],[reports count]);
if (max == 0) max = [reports count];
int i;
for (i = 0; i < max; i++)
[CMCrashReporter submitFile:[reports objectAtIndex:i]];
}
}
}
+(NSArray *)getReports
{
// (Snow) Leopard format is AppName_Year_Month_Day
NSString *prefix = @"RobloxPlayer";
NSMutableArray *array = [NSMutableArray array];
{
NSString *path = [@"~/Library/Logs/CrashReporter/" stringByExpandingTildeInPath];
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
NSString *file;
while (file = [dirEnum nextObject])
if ([file hasPrefix:prefix])
[array addObject:[[NSString stringWithFormat:@"~/Library/Logs/CrashReporter/%@",file] stringByExpandingTildeInPath]];
}
if (false)
{
NSString *path = [@"~/Library/Logs/DiagnosticReports/" stringByExpandingTildeInPath];
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
NSString *file;
while (file = [dirEnum nextObject])
if ([file hasPrefix:prefix])
[array addObject:[[NSString stringWithFormat:@"~/Library/Logs/DiagnosticReports/%@",file] stringByExpandingTildeInPath]];
}
return array;
}
@end
+26
View File
@@ -0,0 +1,26 @@
//
// CMCrashReporterGlobal.h
// CMCrashReporter-App
//
// Created by Jelle De Laender on 20/01/08.
// Copyright 2008 CodingMammoth. All rights reserved.
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface CMCrashReporterGlobal : NSObject {
}
+ (NSString *)appName;
+ (NSString *)version;
+ (BOOL)checkOnCrashes;
+ (NSString *)osVersion;
+ (int)numberOfMaximumReports;
@end
+46
View File
@@ -0,0 +1,46 @@
//
// CMCrashReporterGlobal.m
// CMCrashReporter-App
//
// Created by Jelle De Laender on 20/01/08.
// Copyright 2008 CodingMammoth. All rights reserved.
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
//
// Modified by ROBLOX 2011
#import "CMCrashReporterGlobal.h"
@implementation CMCrashReporterGlobal
+ (NSString *)appName
{
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
}
+ (NSString *)version
{
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
}
+ (int)numberOfMaximumReports {
if (! [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CMMaxReports"])
return 0;
return [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CMMaxReports"] intValue];
}
+ (BOOL)checkOnCrashes
{
// Integration for later
return YES;
}
+ (NSString *)osVersion
{
return [[NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]
objectForKey:@"ProductVersion"];
}
@end
+152
View File
@@ -0,0 +1,152 @@
// This file is used for two targets:
// 1. Used in Mac Roblox Player app, this is the place where actual file resides
// 2. Used in Qt version of Roblox Studio as a soft link from above
#include "FunctionMarshaller.h"
#undef min
#undef max
#include "util/StandardOut.h"
#include "rbx/boost.hpp"
#include "Roblox.h"
using namespace RBX;
FunctionMarshaller::FunctionMarshaller(DWORD threadID)
:refCount(0)
{
this->threadID = threadID;
}
FunctionMarshaller::~FunctionMarshaller()
{
boost::function<void()>* f;
while (asyncCalls.pop_if_present(f))
delete f;
RBXASSERT(threadID == GetCurrentThreadId());
#ifdef _DEBUG
{
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
RBXASSERT (refCount==0);
// Nobody is using this window
RBXASSERT (staticData().windows.find(threadID) == staticData().windows.end());
}
#endif
}
FunctionMarshaller* FunctionMarshaller::GetWindow()
{
// Share a common FunctionMarshaller in a given Thread
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
DWORD threadID = GetCurrentThreadId();
std::map<DWORD, FunctionMarshaller*>::iterator find = staticData().windows.find(threadID);
if (find != staticData().windows.end())
{
// We already created a window, so use it again
find->second->refCount++;
return find->second;
}
else
{
// Create a new window
FunctionMarshaller* window = new FunctionMarshaller(threadID);
staticData().windows[threadID] = window;
window->refCount++;
return window;
}
}
void FunctionMarshaller::ReleaseWindow(FunctionMarshaller* window)
{
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
window->refCount--;
if (window->refCount==0)
{
// Nobody is using this window
staticData().windows.erase(window->threadID);
// window->DestroyWindow();
}
}
void FunctionMarshaller::handleAppEvent(void *pClosure)
{
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
RBX::CEvent *pWaitEvent = closure->waitEvent;
try
{
boost::function<void()>* pF = closure->f;
(*pF)();
delete pF;
delete closure;
}
catch (std::exception& e)
{
StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e);
closure->errorMessage = e.what();
}
// If a task is waiting on an event, set it
if (pWaitEvent)
{
pWaitEvent->Set();
}
}
void FunctionMarshaller::freeAppEvent(void *pClosure)
{
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
boost::function<void()>* pF = closure->f;
delete pF;
delete closure;
}
void FunctionMarshaller::Execute(boost::function<void()> job, CEvent *waitEvent)
{
if (threadID == GetCurrentThreadId())
job();
else
{
Closure *pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = waitEvent;
Roblox::sendAppEvent(pClosure);
}
}
void FunctionMarshaller::Submit(boost::function<void()> job)
{
Closure *pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = NULL;
Roblox::postAppEvent(pClosure);
}
void FunctionMarshaller::ProcessMessages()
{
Roblox::processAppEvents();
}
/*
void FunctionMarshaller::OnFinalMessage(HWND hWnd)
{
delete this;
}
*/
FunctionMarshaller::StaticData::~StaticData()
{
// for (std::map<DWORD, FunctionMarshaller*>::iterator iter = windows.begin(); iter != windows.end(); ++iter)
// iter->second->DestroyWindow();
}
+68
View File
@@ -0,0 +1,68 @@
// This file is used for two targets:
// 1. Used in Mac Roblox Player app, this is the place where actual file resides
// 2. Used in Qt version of Roblox Studio as a soft link from above
#pragma once
#ifdef _WIN32
// This code is platform-specific
#error
#endif
#include <map>
#include "rbx/threadsafe.h"
#include "rbx/CEvent.h"
namespace RBX {
// A very handy class for marshalling a function across Windows threads (sync and async)
class FunctionMarshaller
{
public:
private:
struct StaticData
{
std::map<DWORD, FunctionMarshaller*> windows;
boost::recursive_mutex windowsCriticalSection; // TODO: Would non-recursive be safe here?
~StaticData();
};
SAFE_STATIC(StaticData, staticData)
rbx::safe_queue<boost::function<void()>*> asyncCalls;
int refCount;
DWORD threadID;
FunctionMarshaller(DWORD threadID);
~FunctionMarshaller();
public:
// TODO: Wrap with a reference counter and then remove ~StaticData() cleanup code and remove ReleaseWindow()
static FunctionMarshaller* GetWindow();
static void ReleaseWindow(FunctionMarshaller* window);
static void handleAppEvent(void *pClosure);
static void freeAppEvent(void *pClosure);
struct Closure
{
boost::function<void()>* f;
std::string errorMessage;
RBX::CEvent *waitEvent;
};
void Execute(boost::function<void()> job, CEvent *waitEvent);
void Submit(boost::function<void()> job);
// Call this only from the Window's thread
void ProcessMessages();
// virtual void OnFinalMessage(HWND hWnd);
private:
// LRESULT OnEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
// LRESULT OnAsyncEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
};
}
+58
View File
@@ -0,0 +1,58 @@
/*
* GameVerbs.cpp
* MacClient
*
* Created by Tony on 2/2/11.
* Copyright 2011 __MyCompanyName__. All rights reserved.
*
*/
#include "GameVerbs.h"
#include "RobloxView.h"
LeaveGameVerb::LeaveGameVerb(RobloxView *pRobloxView, VerbContainer* container) :
Verb(container, "Exit")
,robloxView(pRobloxView)
{
}
void LeaveGameVerb::doIt(RBX::IDataState* dataState)
{
if (robloxView)
{
robloxView->leaveGame();
}
}
ShutdownClientVerb::ShutdownClientVerb(RobloxView *pRobloxView, VerbContainer* container) :
Verb( container, "ShutdownClient")
,robloxView(pRobloxView)
{
}
void ShutdownClientVerb::doIt(RBX::IDataState* dataState)
{
if (robloxView)
{
robloxView->shutdownClient();
}
}
ToggleFullscreenVerb::ToggleFullscreenVerb(RobloxView *pRobloxView, VerbContainer* container, VideoControl* videoControl) :
Verb(container, "ToggleFullScreen")
, videoControl(videoControl)
,robloxView(pRobloxView)
{}
void ToggleFullscreenVerb::doIt(RBX::IDataState* dataState)
{
if (robloxView)
{
robloxView->toggleFullScreen();
}
}
bool ToggleFullscreenVerb::isEnabled() const
{
return true;
}
+51
View File
@@ -0,0 +1,51 @@
/*
* GameVerbs.h
* MacClient
*
* Created by Tony on 2/2/11.
* Copyright 2011 __MyCompanyName__. All rights reserved.
*
*/
#pragma once
#include "v8tree/Verb.h"
using namespace RBX;
class LeaveGameVerb : public RBX::Verb
{
class RobloxView *robloxView;
public :
LeaveGameVerb(class RobloxView *pRobloxView, VerbContainer* container);
virtual void doIt(RBX::IDataState* dataState);
};
class ShutdownClientVerb : public RBX::Verb
{
class RobloxView *robloxView;
public :
ShutdownClientVerb( class RobloxView *pRobloxView, VerbContainer* container );
virtual void doIt(RBX::IDataState* dataState);
};
class ToggleFullscreenVerb : public RBX::Verb
{
private:
class VideoControl* videoControl;
class RobloxView *robloxView;
public:
ToggleFullscreenVerb(class RobloxView *pRobloxView, VerbContainer* container, VideoControl* videoControl);
virtual void doIt(RBX::IDataState* dataState);
virtual bool isEnabled() const;
};
+13
View File
@@ -0,0 +1,13 @@
//
// RBXWindow.h
// MacClient
//
// Created by Ben Tkacheff on 9/30/13.
//
//
#import <Cocoa/Cocoa.h>
@interface RBXWindow : NSWindow
@end
+21
View File
@@ -0,0 +1,21 @@
//
// RBXWindow.m
// MacClient
//
// Created by Ben Tkacheff on 9/30/13.
//
//
#import "RBXWindow.h"
@implementation RBXWindow
// necessary override for when game window becomes borderless (otherwise it is never key)
- (BOOL)canBecomeKeyWindow
{
return YES;
}
@end
+44
View File
@@ -0,0 +1,44 @@
//
// RbxWebView.h
// MacClient
//
// Created by Ben Tkacheff on 9/25/13.
//
//
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
typedef enum
{
ROBLOX_APP_SHUTDOWN_CODE_LEAVE_GAME,
ROBLOX_APP_SHUTDOWN_CODE_WINDOW_CLOSE,
ROBLOX_APP_SHUTDOWN_CODE_QUIT
} RobloxAppShutdownCode;
typedef enum
{
GAMETYPE_PLAY,
GAMETYPE_PLAY_PROTOCOL
} RobloxGameType;
typedef enum
{
SUCCESS,
FAILED,
RETRY,
GAME_FULL,
USER_LEFT,
} RequestPlaceInfoResult;
@interface RbxWebView : NSWindowController
{
WebView *webView;
}
-(id) initWithWindowNibName:(NSString*) name;
-(void) dealloc;
@property (weak) IBOutlet WebView *webView;
@end
+92
View File
@@ -0,0 +1,92 @@
//
// RbxWebView.m
// MacClient
//
// Created by Ben Tkacheff on 9/25/13.
//
//
#import "RbxWebView.h"
#import "RobloxPlayerAppDelegate.h"
#include "RobloxView.h"
#include "v8datamodel/GuiService.h"
#include "v8datamodel/Datamodel.h"
FASTSTRING(ClientExternalBrowserUserAgent)
@implementation RbxWebView
@synthesize webView;
-(id) initWithWindowNibName:(NSString*) name
{
if(self = [super initWithWindowNibName:name])
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowWillClose:)
name:NSWindowWillCloseNotification
object:nil];
}
return self;
}
-(void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
static void doInformDataModelUrlWindowClose(RBX::DataModel* dataModel)
{
if (RBX::GuiService* guiService = dataModel->find<RBX::GuiService>())
guiService->urlWindowClosed();
}
- (void) informDataModelUrlWindowClose:(RBX::DataModel*) dataModel
{
if(dataModel)
dataModel->submitTask(boost::bind(&doInformDataModelUrlWindowClose,dataModel), RBX::DataModelJob::Write);
}
- (void)windowDidLoad
{
NSString *versionString;
NSDictionary * sv = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
versionString = [sv objectForKey:@"ProductVersion"];
[self.webView setCustomUserAgent:[NSString stringWithFormat:@"Mozilla/5.0 (Macintosh; Intel Mac OS X %@) AppleWebKit/536.25 (KHTML, like Gecko) %s",versionString, FString::ClientExternalBrowserUserAgent.c_str()]];
}
- (void)windowWillClose:(NSNotification *)notification
{
NSWindow* closingWindow = (NSWindow*)notification.object;
if (closingWindow && closingWindow == self.window )
{
RobloxView* robloxView = NULL;
RobloxPlayerAppDelegate *appDelegate = (RobloxPlayerAppDelegate*)[NSApp delegate];
if(!appDelegate)
return;
if(!appDelegate.robloxView)
return;
robloxView = appDelegate.robloxView;
if (robloxView)
{
[self informDataModelUrlWindowClose:robloxView->getDataModel().get()];
}
[self.window resignFirstResponder];
[self.window resignMainWindow];
[self.webView setResourceLoadDelegate:nil];
[self autorelease];
}
}
@end
+169
View File
@@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13A603" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment defaultVersion="1060" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="847" id="849"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="371" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="29">
<items>
<menuItem title="Roblox" id="56">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Roblox" systemMenu="apple" id="57">
<items>
<menuItem title="About Roblox" id="58">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="236"/>
<menuItem title="Hide Roblox" keyEquivalent="h" id="134">
<connections>
<action selector="hide:" target="-1" id="367"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="145">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="368"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="149"/>
<menuItem title="Quit Roblox" keyEquivalent="q" id="136">
<connections>
<action selector="terminate:" target="-1" id="369"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="1014">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="1015">
<items>
<menuItem title="Undo" keyEquivalent="z" id="1016">
<connections>
<action selector="undo:" target="-1" id="1082"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="1017">
<connections>
<action selector="redo:" target="-1" id="1076"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="1018"/>
<menuItem title="Cut" keyEquivalent="x" id="1019">
<connections>
<action selector="cut:" target="-1" id="1062"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="1020">
<connections>
<action selector="copy:" target="-1" id="1061"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="1021">
<connections>
<action selector="paste:" target="-1" id="1067"/>
</connections>
</menuItem>
<menuItem title="Delete" id="1023">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="1077"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="1024">
<connections>
<action selector="selectAll:" target="-1" id="1080"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Test" id="83">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Test" id="81">
<items>
<menuItem title="Open…" keyEquivalent="o" id="72">
<connections>
<action selector="openFile:" target="847" id="859"/>
</connections>
</menuItem>
<menuItem title="Crash" id="935">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="crash:" target="-1" id="937"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="19">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="24">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="23">
<connections>
<action selector="performMiniaturize:" target="-1" id="37"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="239">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="240"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="92"/>
<menuItem title="Bring All to Front" id="5">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="39"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<window title="Roblox" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" visibleAtLaunch="NO" frameAutosaveName="Main Window" animationBehavior="default" id="843" customClass="RBXWindow">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="300" y="300" width="1000" height="665"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/>
<value key="minSize" type="size" width="800" height="600"/>
<view key="contentView" id="844">
<rect key="frame" x="0.0" y="0.0" width="1000" height="665"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<customView id="850" customClass="RobloxOgreView">
<rect key="frame" x="0.0" y="0.0" width="1000" height="665"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</customView>
</subviews>
</view>
<connections>
<outlet property="delegate" destination="847" id="897"/>
</connections>
</window>
<customObject id="847" customClass="RobloxPlayerAppDelegate">
<connections>
<outlet property="mainMenu" destination="29" id="896"/>
<outlet property="mainView" destination="844" id="1116"/>
<outlet property="ogreView" destination="850" id="851"/>
<outlet property="webPlayer" destination="855" id="856"/>
<outlet property="window" destination="843" id="uIW-gx-V8c"/>
</connections>
</customObject>
<customObject id="855" customClass="RobloxWebPlayer"/>
</objects>
</document>
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9060" systemVersion="14F1021" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9060"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="9060"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="RbxWebView">
<connections>
<outlet property="webView" destination="E3k-K2-jx9" id="qr1-ZM-P7H"/>
<outlet property="window" destination="tlP-T8-JXC" id="09L-rq-dOQ"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="ROBLOX" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" oneShot="NO" animationBehavior="default" id="tlP-T8-JXC">
<windowStyleMask key="styleMask" titled="YES" texturedBackground="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="760" y="331" width="940" height="650"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1058"/>
<view key="contentView" id="UPg-5a-BO3">
<rect key="frame" x="0.0" y="0.0" width="940" height="650"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<webView id="E3k-K2-jx9">
<rect key="frame" x="0.0" y="0.0" width="1024" height="650"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<webPreferences key="preferences" defaultFontSize="12" defaultFixedFontSize="12" javaScriptCanOpenWindowsAutomatically="NO">
<nil key="identifier"/>
</webPreferences>
</webView>
</subviews>
</view>
</window>
</objects>
</document>
Binary file not shown.
+217
View File
@@ -0,0 +1,217 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Roblox</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"
type="text/javascript"></script>
</head>
<body id="BODY1" style="padding: 0; margin: 0">
<!-- Main -->
<div id="Main" style="position: absolute; top: 0; left: 0; bottom: 0; right: 0; margin-bottom: 40px;
margin-right: 200px; padding: 0 1px 1px 0">
</div>
<!-- TODO: nuke the Panel div when we don't need it for debugging -->
<div id="Panel" style="position: absolute; top: 0; right: 0; bottom: 0; width: 200px;
margin-bottom: 40px; overflow: auto; border-left-style: solid; border-width: 1px">
<div id="outputPanel" style="width: 100%">
</div>
<script type="text/javascript">
var traces = new Array();
var traceIndex = 19;
function trace(message) {
var div = $('<div></div>')
div.text(message);
div.appendTo(outputPanel);
if (traces.length < 20)
traces.push(div);
else {
traceIndex = traceIndex + 1;
if (traceIndex == 20)
traceIndex = 0;
traces[traceIndex].remove();
traces[traceIndex] = div;
}
}
</script>
</div>
<!-- Taskbar -->
<div id="Taskbar" style="background-color: Window; padding: 5px; height: 30px; position: absolute;
left: 0; right: 0; bottom: 0; position: absolute; bottom: 0; left: 0; border-width: 1px;
border-top-style: solid">
<div id="CommonButtons" style="float: right; margin: 5px">
<button onclick="window.external.IsFullscreen = !window.external.IsFullscreen; return false">
Fullscreen</button>
<button onclick="focusContent(); window.external.Quit(); return false">
Quit</button>
</div>
<div id="block" style="float: right; background-color: #abc; width: 20px; height: 20px;
margin: 5px">
</div>
<script type="text/javascript">
function animateGrow() {
$(block).animate({ width: "+=20px" }, { duration: "slow", complete: animateShrink });
}
function animateShrink() {
$(block).animate({ width: "-=20px" }, { duration: "slow", complete: animateGrow });
}
$(animateGrow);
</script>
<div id="GameButtons" style="display: inline">
<button onclick="roblox3D.DoVerb('ReportAbuse'); return false" style="display: inline">
Report Abuse</button>
<button onclick="showWeb(); return false">
Leave Game</button>
<div id="FPS" style="display: inline">
</div>
</div>
<div id="WebButtons" style="display: none">
<button id="playButton" onclick="play(); return false" style="display: inline">
Play</button>
<button id="Button2" onclick="createView(100); return false" style="display: inline">
CreateView 100</button>
</div>
</div>
<!-- Scripts -->
<script type="text/javascript">
var content;
function focusContent() {
// TODO: This code is ugly
if (document.getElementById('roblox3D'))
document.getElementById('roblox3D').focus();
else if (document.getElementById('robloxFrame'))
document.getElementById('robloxFrame').focus();
}
var windowed = true;
function create3D(callback) {
if (content)
$(content).remove();
$(WebButtons).hide('slow', function() {
Main.innerHTML = '<object id="roblox3D" classid="CLSID:D7EB14E2-66D2-4A6E-A50A-666CBE7A7621" width="100%" height="100%"><param name="windowed" value="' + (windowed ? 'true' : 'false') + '"></object>';
windowed = !windowed;
content = $(roblox3D);
roblox3D.WhenReady({
success: function(result) {
focusContent();
callback();
$(GameButtons).show();
},
error: showWeb
});
});
}
function createView(count) {
if (count == 0)
return;
create3D(function() {
window.setTimeout(function() {
trace("CreateView " + count);
showWeb(function() {
window.setTimeout(function() {
createView(count - 1);
}, 1000);
});
}, 1000);
});
}
function getRobloxBrowserUrl() {
var iframe = document.getElementById("robloxFrame");
return iframe.contentWindow.location.href;
}
// Displays the web browser in an ifram
function showWeb(callback) {
if (document.getElementById('roblox3D'))
roblox3D.ShutDown();
if (content)
$(content).remove();
$(GameButtons).hide('slow', function() {
content = $('<iframe id="robloxFrame" width="100%" frameborder="0" />');
var loaded = false;
// Define a callback to handle iframe loads and to update the Play button
$(content).load(function() {
if (loaded)
return;
loaded = true;
$(WebButtons).show('slow');
if (callback)
callback();
});
// TODO: Replace "http://www.watrbx.wtf/" with proper domain
$(content).attr('src', "http://www.watrbx.wtf/Games.aspx");
$(content).appendTo(Main);
// Handle resize events:
$(content).height($(Main).height());
// TODO: This only works if the window resizes. If other elements in the layout changes
// then it won't get notified
$(window).resize(function() {
$(content).height($(Main).height());
});
});
}
function errorAlert(message, stack) {
var s = message + '\n' + stack;
alert(s);
}
function bindFPS() {
roblox3D.Lua("f = ... while true do local render = stats():FindFirstChild('Render') if render then f(render['3D CPU Total']:GetValueString()) end wait(1) end", {
args: [function(value) {
if (value != '?' && roblox3D.IsWindowless)
value = value + " windowless";
FPS.innerText = value;
} ],
error: errorAlert
});
}
// Loads a the 3D view and then loads a game based on what was displayed in the iframe
function play() {
// TODO: getRobloxBrowserUrl().match() to find the place ID
// TODO: Replace "http://www.watrbx.wtf/" with proper domain
var gameUrl = 'http://www.watrbx.wtf/Asset/?id=1437';
create3D(function() {
bindFPS();
return;
// TODO: Replace "http://www.watrbx.wtf/" with proper domain
// TODO: Replace visit script with multiplayer join (should be done with jquery or json or something)
roblox3D.Lua("gameUrl = ... game:Load(gameUrl)", {
args: [gameUrl],
success: function() {
roblox3D.Lua("http://www.watrbx.wtf/game/visit.ashx");
},
error: function(message, stack) { alert(stack); }
});
});
}
// TODO: Nuke this when the Panel div goes away
window.external.onStandardOut = function(type, message) { trace(message); };
// Startup:
$(play);
</script>
</body>
</html>
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Roblox</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"
type="text/javascript"></script>
</head>
<body id="BODY1" style="padding: 0; margin: 0">
<!-- Main -->
<!-- TODO: nuke the Panel div when we don't need it for debugging -->
<div id="WebButtons">
<button id="playButton" onclick="play(); return false" style="display: inline">
Play</button>
</div>
<div id="outputPanel" style="width: 100%">
output
</div>
<script type="text/javascript">
var traces = new Array();
var traceIndex = 19;
function trace(message) {
var div = $('<div></div>')
div.text(message);
div.appendTo(outputPanel);
if (traces.length < 20)
traces.push(div);
else {
traceIndex = traceIndex + 1;
if (traceIndex == 20)
traceIndex = 0;
traces[traceIndex].remove();
traces[traceIndex] = div;
}
}
</script>
<!-- Scripts -->
<script type="text/javascript">
function play() {
trace("playing!");
//window.external.StartGame();
trace(window.external.helloWorld());
trace(window.external.IsRobloxAppIDE);
trace(window.external.InstallHost);
window.external.StartGame(
'2D86BB6A7DE07B38CCE1D50A191BAB5FCF43A5BA1FCF1EF9B7A7F775F70EF41A6150828C9A7D61BA1B09326F124D37939899147D8992519A2DB3980A901E14736B3E3AD064BE8EF2BB1A356EC92AF9C4432FAF5BF0F51AA6CEF00210E7D6007DE205D298CB7F65822A0CD52C04515B484318ADDF389345797D33DCAA1B74472159A4BC2F3168067F177E0F5853074F83C335E0C06E0B80F19CE02E954FBDB91828C41DC31700D53E9832D664EB7D719CBBAD708D7882A8513FB6E570D38D9906A5C02C363D24B6FF78D47EF2020FC1A1EB152819',
'http://sitetest.watrbx.wtf//Login/Negotiate.ashx',
'http://sitetest.watrbx.wtf//Game/visit.ashx?PlaceID=2562360&upload=2562360'
);
trace("played!");
}
function go() {
trace("hi there!");
}
$(go);
</script>
</body>
</html>
+74
View File
@@ -0,0 +1,74 @@
This program uses the Graphics3D Library ("G3D", http://g3d-cpp.sf.net)
NONE OF THESE LIBRARIES REQUIRE *THIS* PROGRAM TO BE OPEN SOURCE.
This program uses the Graphics3D Library ("G3D", http://g3d-cpp.sf.net), which
is licensed under the "BSD" Open Source license. The Graphics3D library
source code is Copyright © 2000-2004, Morgan McGuire, All rights reserved.
------------------
FMOD Sound System, copyright © Firelight Technologies Pty, Ltd., 1994-2007.
------------------
Roblox uses the Lua language and code. The Lua portion of the product is
under the following license ("Software" refers to Lua, not ROBLOX):
Copyright © 1994Ð2010 Lua.org, PUC-Rio.
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.
------------------
Google Breakpad:
Copyright (c) 2006, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+2
View File
@@ -0,0 +1,2 @@
Plugin=RenderSystem_GL
Plugin=Plugin_ParticleFX
+16
View File
@@ -0,0 +1,16 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" id="www-roblox-com">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js" type="text/javascript"></script>
</head>
<body>
<center>
<p>ROBLOX</p>
<button onclick="window.external.Play()">
Play</button>
<button onclick="window.external.Load()">
Load</button>
</center>
</body>
</html>
+357
View File
@@ -0,0 +1,357 @@
/*
* RobloxCpp.cpp
* MacClient
*
* Created by Tony on 1/26/11.
* Copyright 2011 __MyCompanyName__. All rights reserved.
*
*/
#include "Roblox.h"
#undef max
#undef min
#include "v8datamodel/datamodel.h"
#include "v8datamodel/workspace.h"
#include "v8datamodel/partinstance.h"
#include "v8datamodel/factoryregistration.h"
#include "Util/FileSystem.h"
#include "Util/Http.h"
#include "Util/Profiling.h"
#include "Util/Statistics.h"
#include "Util/MD5Hasher.h"
#include "util/RobloxGoogleAnalytics.h"
#include "v8datamodel/game.h"
#include "v8datamodel/GameSettings.h"
#include "v8datamodel/DebugSettings.h"
#include "v8datamodel/PhysicsSettings.h"
#include "v8datamodel/ContentProvider.h"
#include "v8datamodel/GlobalSettings.h"
#include "script/ScriptContext.h"
#include "script/LuaSettings.h"
#include "network/api.h"
#include "rbx/ProcessPerfCounter.h"
#include "rbx/Profiler.h"
#include "Gui/ProfanityFilter.h"
#include "boost/filesystem.hpp"
#include "MachineConfiguration.h"
#include <string>
FASTFLAG(GoogleAnalyticsTrackingEnabled)
DYNAMIC_LOGGROUP(GoogleAnalyticsTracking)
LOGGROUP(Network)
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
bool Roblox::initialized = false;
std::string Roblox::assetFolder;
Roblox::RunMode Roblox::runMode = Roblox::RUN_FILE;
rbx::signals::scoped_connection messageOutConnection;
RBX::ClientAppSettings Roblox::appSettings;
static boost::shared_ptr<RBX::Game> preloadedGame;
static boost::mutex preloadedGameMutex;
static bool preloadShuttingDown = false;
static boost::thread releaseGameThread;
extern "C" {
void writeFastLogDumpHelper(const char *fileName, int numEntries)
{
FLog::WriteFastLogDump(fileName, numEntries);
}
};
static void do_preloadGame(bool isApp)
{
// Avoid having 2 DataModels open at once
// It should be safe, but may as well not play with fire
releaseGameThread.join();
boost::mutex::scoped_lock lock(preloadedGameMutex);
if (preloadShuttingDown)
return;
if (!preloadedGame)
{
RBX::Time start = RBX::Time::now<RBX::Time::Fast>();
preloadedGame.reset(new RBX::SecurePlayerGame(NULL, ::GetBaseURL().c_str()));
RBX::Time stop = RBX::Time::now<RBX::Time::Fast>();
double secs = (stop - start).seconds();
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Preloaded Game %gsec", secs);
}
else
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Already preloaded Game");
}
void Roblox::preloadGame(bool isApp)
{
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Requesting preload Game");
boost::thread(boost::bind(&do_preloadGame, isApp));
}
extern std::string macBundlePath();
bool Roblox::globalInit(bool isApp)
{
if (initialized)
{
return true;
}
RBX::Game::globalInit(false);
std::string fileRobloxPlayer = macBundlePath() + "/Contents/MacOS/RobloxPlayer";
// hash may have been overridden on command line, otherwise calculate from executable
if (RBX::DataModel::hash.length() == 0)
{
RBX::DataModel::hash = RBX::CollectMd5Hash(fileRobloxPlayer);
}
RBX::ContentProvider::setAssetFolder(assetFolder.c_str());
// Reset synchronized flags, they should be set by the server
FLog::ResetSynchronizedVariablesState();
{
bool useCurl = rand() % 100 < RBX::ClientAppSettings::singleton().GetValueHttpUseCurlPercentageMacClient();
FASTLOG1(FLog::Network, "Use CURL = %d", useCurl);
RBX::Http::SetUseCurl(useCurl);
RBX::Http::SetUseStatistics(true);
}
{
int lottery = rand() % 100;
FASTLOG1(DFLog::GoogleAnalyticsTracking, "Google analytics lottery number = %d", lottery);
// initialize google analytics
if (FFlag::GoogleAnalyticsTrackingEnabled && (lottery < RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsLoadPlayer()))
{
RBX::RobloxGoogleAnalytics::setCanUseAnalytics();
RBX::RobloxGoogleAnalytics::init(RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsAccountPropertyIDPlayer(),
RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsThreadPoolMaxScheduleSize());
}
}
// must be after globalInit, where http pool is initialized
RBX::postMachineConfiguration(::GetBaseURL().c_str(), 0);
messageOutConnection = RBX::StandardOut::singleton()->messageOut.connect(&onMessageOut);
RBX::GlobalAdvancedSettings::singleton()->loadState("");
{
RBX::Security::Impersonator impersonate(RBX::Security::RobloxGameScript_);
RBX::GlobalBasicSettings::singleton()->loadState("");
}
RBX::Profiler::onThreadCreate("Main");
// Initialize the TaskScheduler (after loading configs)
RBX::TaskScheduler::singleton().setThreadCount(RBX::TaskSchedulerSettings::singleton().getThreadPoolConfig());
initialized = true;
preloadGame(isApp);
return true;
}
boost::shared_ptr<RBX::Game> Roblox::getpreloadedGame(const bool isApp)
{
boost::mutex::scoped_lock lock(preloadedGameMutex);
if (!preloadedGame)
{
RBX::Time start = RBX::Time::now<RBX::Time::Fast>();
preloadedGame.reset(new RBX::SecurePlayerGame(NULL, ::GetBaseURL().c_str()));
RBX::Time stop = RBX::Time::now<RBX::Time::Fast>();
double secs = (stop - start).seconds();
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Loaded Game %gsec", secs);
}
boost::shared_ptr<RBX::Game> temp = preloadedGame;
preloadedGame.reset();
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
temp->getDataModel()->create<RBX::ScriptContext>();
return temp;
}
static void releaseGame(boost::shared_ptr<RBX::Game> game)
{
RBX::GlobalBasicSettings::singleton()->saveState();
game->shutdown();
}
void Roblox::relinquishGame(boost::shared_ptr<RBX::Game>& game)
{
releaseGameThread.join();
// Defer deletion of DataModel for later (so that we don't block)
releaseGameThread = boost::thread(boost::bind(&releaseGame, game));
game.reset();
}
void Roblox::globalShutdown()
{
{
boost::mutex::scoped_lock lock(preloadedGameMutex);
preloadShuttingDown = true;
if (preloadedGame)
preloadedGame->shutdown(); // Don't rely on destructor, in case somebody else holds a reference
preloadedGame.reset();
}
// Don't exit until Game has been shut down. Otherwise we might get crashes in static destructors
releaseGameThread.join();
if (!initialized)
return;
RBX::Game::globalExit();
messageOutConnection.disconnect();
}
void doCrash()
{
RBXCRASH();
}
void Roblox::testCrash()
{
boost::shared_ptr<RBX::Instance> i = RBX::Creatable<RBX::Instance>::create<RBX::PartInstance>();
i->propertyChangedSignal.connect(boost::bind(&doCrash));
i->setName("blah");
}
void Roblox::setArgs(const char *gameFolder, const char *runMode)
{
Roblox::assetFolder = std::string(gameFolder) + "/content/";
if (strcmp(runMode,"c") == 0){
Roblox::runMode = RUN_CLIENT;
}
else if (strcmp(runMode,"s") == 0){
Roblox::runMode = RUN_SERVER;
}
else {
Roblox::runMode = RUN_FILE;
}
}
extern "C" {
void setRobloxArgs(const char *gameFolder, const char *runMode)
{
Roblox::setArgs(gameFolder, runMode);
}
}
// Utility functions
void Roblox::onMessageOut(const RBX::StandardOutMessage& message)
{
switch (message.type)
{
case RBX::MESSAGE_INFO:
printf("INFO: %s\n", message.message.c_str());
break;
case RBX::MESSAGE_WARNING:
printf("WARNING: %s\n", message.message.c_str());
break;
case RBX::MESSAGE_ERROR:
printf("ERROR: %s\n", message.message.c_str());
break;
default:
printf("%s\n", message.message.c_str());
break;
}
}
static void handler(std::exception* ex, std::string file)
{
if (ex)
return;
std::remove(file.c_str());
}
void uploadAndDeletFileAsync(const char* url, const char* file)
{
boost::shared_ptr<std::fstream> data(new std::fstream(file, std::ios_base::in | std::ios_base::binary));
size_t begin = data->tellg();
data->seekg (0, std::ios::end);
size_t end = data->tellg();
if (end > begin)
{
data->seekg (0, std::ios::beg);
std::string version;
while (*data)
{
char buff[255];
data->getline(buff, 155);
std::string line = buff;
if (line.substr(0, 8) == "Version:")
{
// "Version: 0.34.0.107 (107)"
for (size_t i = 8; i < line.size(); ++i)
if (line[i] != ' ')
{
line = line.substr(i);
break;
}
// "0.34.0.107 (107)"
for (size_t i = 0; i < line.size(); ++i)
if (line[i] == ' ')
{
line = line.substr(0, i);
// "0.34.0.107"
while (true)
{
int j = line.find('.');
if (j == std::string::npos)
break;
line = line.replace(j, 1, ",%20");
}
// "0,%2034,%200,%20107"
version = line;
break;
}
}
}
data->seekg (0, std::ios::beg);
boost::filesystem::path p(file);
std::string filename = "log_";
// extract file "guid"
filename += p.filename().string().substr(13, 17);
filename += "%20";
filename += version;
filename += ".crash";
std::string fullUrl = url;
fullUrl += "?filename=" + filename;
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Uploading %s", fullUrl.c_str());
RBX::Http(fullUrl).post(data, RBX::Http::kContentTypeUrlEncoded, true, boost::bind(handler, _2, p.string()));
}
}
+83
View File
@@ -0,0 +1,83 @@
/*
* Roblox.h
* MacClient
*
* Created by Tony on 1/26/11.
* Copyright 2011 __MyCompanyName__. All rights reserved.
*
*/
#include "Util/StandardOut.h"
#include <v8datamodel/TeleportService.h>
#include <semaphore.h>
#include <v8datamodel/FastLogSettings.h>
extern void callTerminateApp();
namespace RBX
{
class Game;
}
class Roblox
{
public :
enum RunMode {
RUN_CLIENT,
RUN_SERVER,
RUN_FILE,
RUN_DEVELOPER,
};
private :
static boost::scoped_ptr<boost::thread> singleRunningInstance;
static sem_t *uniqSemaphore;
static bool needTerminateCall;
static shared_ptr<RBX::TeleportCallback> callback;
static void *theInstance;
static bool initialized;
static std::string assetFolder;
static RunMode runMode;
static RBX::ClientAppSettings appSettings;
static void onMessageOut(const RBX::StandardOutMessage& message);
static void globalShutdown();
static void terminateWaiter();
static bool isOtherRunning();
static void terminateOther();
public :
static bool globalInit(bool isApp);
static bool initInstance(void *instance, bool isApp);
static void shutdownInstance();
static void releaseTerminateWaiter();
static void setArgs(const char *gameFolder, const char *runMode);
static void sendAppEvent(void *pClosure);
static void postAppEvent(void *pClosure);
static void processAppEvents();
static void addLogToBreakpad(const char* log);
static void addBreakPadKeyValue(const char* key, int value);
// Specific handlers for verbs events etc.
static void handleLeaveGame(void *appWindow);
static void handleShutdownClient(void *appWindow);
static void handleToggleFullScreen(void *appWindow);
static bool inFullScreenMode(void *appWindow);
static RunMode getRunMode() { return runMode; }
static void preloadGame(bool isApp);
static boost::shared_ptr<RBX::Game> getpreloadedGame(const bool isApp);
static void relinquishGame(boost::shared_ptr<RBX::Game>& game);
static void testCrash();
};
+378
View File
@@ -0,0 +1,378 @@
/*
* Roblox.cpp
* MacClient
*
* Created by Tony on 1/26/11.
* Copyright 2011 __MyCompanyName__. All rights reserved.
*
*/
#import <Cocoa/Cocoa.h>
#import "RobloxPlayerAppDelegate.h"
#import "CMCrashReporter.h"
#include "Roblox.h"
#include "FunctionMarshaller.h"
#include "rbx/CEvent.h"
#include "rbx/log.h"
#include "util/guid.h"
#include "Util/FileSystem.h"
#include "RobloxView.h"
#include "v8datamodel/TeleportCallback.h"
#include "FastLog.h"
#include <semaphore.h>
const std::string& GetBaseURL();
void callTerminateApp()
{
[NSApp terminate:nil];
}
void *Roblox::theInstance = NULL;
boost::scoped_ptr<boost::thread> Roblox::singleRunningInstance;
sem_t *Roblox::uniqSemaphore = NULL;
bool Roblox::needTerminateCall = true;
shared_ptr<RBX::TeleportCallback> Roblox::callback;
// This function will locate the path to our application on OS X,
// unlike windows you cannot rely on the current working directory
// for locating your configuration files and resources.
std::string macBundlePath()
{
char path[1024];
CFBundleRef mainBundle = CFBundleGetMainBundle();
assert(mainBundle);
CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
assert(mainBundleURL);
CFStringRef cfStringRef = CFURLCopyFileSystemPath( mainBundleURL, kCFURLPOSIXPathStyle);
assert(cfStringRef);
CFStringGetCString(cfStringRef, path, 1024, kCFStringEncodingASCII);
CFRelease(mainBundleURL);
CFRelease(cfStringRef);
return std::string(path);
}
class LogProvider : public RBX::ILogProvider
{
boost::thread_specific_ptr<RBX::Log> log;
boost::filesystem::path logDir;
boost::mutex breakPadLogMutex;
int breakpadCount;
std::vector<RBX::Log*> fastLogChannels;
std::string logGuid;
static RBX::mutex fastLogChannelsLock;
static LogProvider* mainLogManager;
public:
LogProvider()
:breakpadCount(0)
{
RBX::Guid::generateRBXGUID(logGuid);
logGuid = logGuid.substr(3, 6);
logDir = RBX::FileSystem::getLogsDirectory();
std::cout << "Writing logs to " << logDir.string() << " with GUID " + logGuid << '\n';
mainLogManager = this;
FLog::SetExternalLogFunc(LogProvider::FastLogMessage);
}
~LogProvider()
{
// Do not delete Log files on Mac Player exit
// Look for Log under /tmp/roblox.xyz/Roblox/Logs
// tmp folder is auto cleaned on restart of Mac
//RBX::FileSystem::clearCacheDirectory("Logs");
}
virtual RBX::Log* provideLog()
{
RBX::Log* result = log.get();
if (!result)
{
std::string name = RBX::get_thread_name();
boost::filesystem::path logFile = logDir / ("log_" + logGuid + ".txt");
result = new RBX::Log(logFile.c_str(), name.c_str());
log.reset(result);
boost::mutex::scoped_lock lock(breakPadLogMutex);
if (breakpadCount++ < 10)
{
Roblox::addLogToBreakpad(logFile.c_str());
}
}
return result;
}
static void FastLogMessage(FLog::Channel id, const char* message) {
RBX::mutex::scoped_lock lock(fastLogChannelsLock);
if(mainLogManager)
{
if(id >= mainLogManager->fastLogChannels.size())
mainLogManager->fastLogChannels.resize(id+1, NULL);
if(mainLogManager->fastLogChannels[id] == NULL)
{
char temp[20];
snprintf(temp, 19, "log_%s_%u.txt", mainLogManager->logGuid.c_str(), id);
temp[19] = 0;
boost::filesystem::path logFile = mainLogManager->logDir / temp;
mainLogManager->fastLogChannels[id] = new RBX::Log(logFile.c_str(), "Log Channel");
Roblox::addLogToBreakpad(logFile.c_str());
}
mainLogManager->fastLogChannels[id]->writeEntry(RBX::Log::Information, message);
}
}
static void WriteFastLogDump(){
RBX::mutex::scoped_lock lock(fastLogChannelsLock);
if(mainLogManager)
{
boost::filesystem::path logFile = mainLogManager->logDir / ("log_" + mainLogManager->logGuid + "_dump.txt");
FLog::WriteFastLogDump(logFile.c_str(), 2048);
Roblox::addLogToBreakpad(logFile.c_str());
}
}
};
void MacWriteFastLogDump()
{
LogProvider::WriteFastLogDump();
}
LogProvider* LogProvider::mainLogManager = NULL;
RBX::mutex LogProvider::fastLogChannelsLock;
static LogProvider logProvider;
class Teleporter : public RBX::TeleportCallback
{
void *_instance;
public:
Teleporter(void *instance) { _instance = instance; }
virtual void doTeleport(const std::string &url, const std::string &ticket, const std::string &script)
{
RobloxView *view = nil;
RobloxPlayerAppDelegate *appDelegate = (RobloxPlayerAppDelegate*)_instance;
view = [appDelegate robloxView];
view->stopJobs();
view->marshalTeleport(url, ticket, script);
}
virtual bool isTeleportEnabled() const { return true; }
};
void Roblox::addLogToBreakpad(const char* log)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSResponder *responder = (NSResponder *) theInstance;
NSString* s = [NSString stringWithFormat:@"%s", log];
[responder performSelectorOnMainThread:@selector(addLogFile:)
withObject:s waitUntilDone:NO];
[pool release];
}
void Roblox::addBreakPadKeyValue(const char* key, int value)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSResponder *responder = (NSResponder *) theInstance;
NSString* s = [NSString stringWithFormat:@"%s", key];
NSString* v = [NSString stringWithFormat:@"%d", value];
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:s, @"key", v, @"value", nil];
[responder performSelectorOnMainThread:@selector(addBreakPadKeyValueFromDictionary:)
withObject:info waitUntilDone:NO];
[pool release];
}
static char uniqPlayerId[] = "/RobloxPlayerUniq";
bool Roblox::isOtherRunning()
{
pid_t id = getpid();
///NSLog(@"isOtherRunning - pid = %i trying to open semaphore", id);
sem_t *sem = sem_open(uniqPlayerId, O_CREAT | O_EXCL, 0666, 0);
if (sem != SEM_FAILED)
{
NSLog(@"isOtherRunning - pid = %i waiting on semaphore", id);
uniqSemaphore = sem; // I guess we need atomic echange????
sem_wait(sem);
uniqSemaphore = NULL;
NSLog(@"isOtherRunning - pid = %i closing semaphore", id);
sem_close(sem);
sem_unlink(uniqPlayerId);
return false;
}
//NSLog(@"isOtherRunning - pid = %i semaphore open failed", id);
return true;
}
void Roblox::terminateOther()
{
sem_t *sem = sem_open(uniqPlayerId, 0);
if (sem != SEM_FAILED)
{
//NSLog(@"terminateOther - pid = %i semaphore is opened", id);
sem_post(sem);
sem_close(sem);
}
}
void Roblox::terminateWaiter()
{
pid_t id = getpid();
NSLog(@"Roblox::terminateWaiter - pid = %i", id);
while(isOtherRunning())
{
terminateOther();
usleep(1000*100); //sleep 100 msecs
}
if (needTerminateCall)
{
NSLog(@"Roblox::terminateWaiter - pid = %i, terminating app", id);
[NSApp terminate:nil];
}
}
bool Roblox::initInstance(void *instance, bool isApp)
{
singleRunningInstance.reset(new boost::thread(&Roblox::terminateWaiter));
theInstance = instance;
callback = shared_ptr<RBX::TeleportCallback>(new Teleporter(instance));
RBX::TeleportService::SetCallback(callback.get());
RBX::TeleportService::SetBaseUrl(::GetBaseURL().c_str());
RBX::Log::setLogProvider(&logProvider);
if(globalInit(isApp))
{
[CMCrashReporter check];
return true;
}
return false;
}
void Roblox::releaseTerminateWaiter()
{
if (uniqSemaphore != NULL)
{
pid_t id = getpid();
NSLog(@"Roblox::shutdownInstance - pid = %i, thread is still waiting, signaling semaphore", id);
needTerminateCall = false;
sem_post(uniqSemaphore);
}
}
void Roblox::shutdownInstance()
{
releaseTerminateWaiter();
globalShutdown();
theInstance = NULL;
}
// FunctionMarshaller help - posts worker thread message to UI thread and deals with it next time through
void Roblox::sendAppEvent(void *pClosure)
{
NSGraphicsContext *context = nil;
NSInteger windowNumber = 0;
NSPoint location;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSEvent *ev = [[NSEvent otherEventWithType:NSApplicationDefined location:location modifierFlags:0 timestamp:0 windowNumber:windowNumber context:context
subtype:0 data1:(NSInteger)pClosure data2:0] retain];
RBX::CEvent *waitEvent = ((RBX::FunctionMarshaller::Closure *) pClosure)->waitEvent;
BOOL waitFlag = (waitEvent == NULL);
// TBD: Make sure the marshallFunction: selector actually exists!
NSResponder *responder = (NSResponder *) theInstance;
[responder performSelectorOnMainThread:@selector(marshallFunction:) withObject:ev waitUntilDone:waitFlag];
if (waitEvent)
{
waitEvent->Wait();
}
[pool release];
}
void Roblox::postAppEvent(void *pClosure)
{
NSGraphicsContext *context = nil;
NSInteger windowNumber = 0;
NSPoint location;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSEvent *ev = [[NSEvent otherEventWithType:NSApplicationDefined location:location modifierFlags:0 timestamp:0 windowNumber:windowNumber context:context
subtype:0 data1:(NSInteger)pClosure data2:0] retain];
// TBD: Make sure the marshallFunction: selector actually exists!
NSResponder *responder = (NSResponder *) theInstance;
[responder performSelectorOnMainThread:@selector(marshallFunction:) withObject:ev waitUntilDone:NO];
[pool release];
}
void Roblox::processAppEvents()
{
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource)
;
}
void Roblox::handleLeaveGame(void *appWindow)
{
RobloxPlayerAppDelegate *delegate = (RobloxPlayerAppDelegate *) appWindow;
[delegate handleLeaveGame];
}
void Roblox::handleShutdownClient(void *appWindow)
{
RobloxPlayerAppDelegate *delegate = (RobloxPlayerAppDelegate *) appWindow;
[delegate handleShutdownClient];
}
void Roblox::handleToggleFullScreen(void *appWindow)
{
RobloxPlayerAppDelegate *delegate = (RobloxPlayerAppDelegate *) appWindow;
[delegate handleToggleFullScreen];
}
bool Roblox::inFullScreenMode(void *appWindow)
{
RobloxPlayerAppDelegate *delegate = (RobloxPlayerAppDelegate *) appWindow;
bool fullscreen = [delegate inFullScreenMode];
return fullscreen;
}
+76
View File
@@ -0,0 +1,76 @@
//
// RobloxOgreView.h
// MacClient
//
// Created by Tony on 12/13/10.
// Copyright 2010 Roblox. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface RobloxOgreView : NSView
{
class RobloxView *robloxView;
NSResponder *appDelegate;
NSTrackingArea* trackingArea;
BOOL cursorHidden;
BOOL controlKeyWasDown;
BOOL fullScreen;
CGPoint virtualMousePosition;
NSRect nonFullScreenRect;
NSInteger nonFullScreenWindowLevel;
}
@property (assign) BOOL cursorHidden;
@property (assign) BOOL controlKeyWasDown;
@property (assign) BOOL fullScreen;
-(id)initWithFrame:(NSRect)f;
-(CGPoint) getVirtualCursorPos;
-(void) setVirtualCursorPos:(CGPoint) newPos;
-(void) mouseDown:(NSEvent *)event;
-(void) mouseDragged:(NSEvent *)event;
-(void) mouseMoved:(NSEvent *)event;
-(void) mouseUp:(NSEvent *)event;
-(void) rightMouseDown:(NSEvent *)event;
-(void) rightMouseDragged:(NSEvent *)event;
-(void) rightMouseUp:(NSEvent *)event;
-(void) otherMouseDown:(NSEvent *)event;
-(void) otherMouseDragged:(NSEvent *)event;
-(void) otherMouseUp:(NSEvent *)event;
-(void) mouseEntered:(NSEvent *)theEvent;
-(void) mouseExited:(NSEvent *)theEvent;
-(void) keyDown:(NSEvent *)event;
-(void) keyUp:(NSEvent *)event;
-(void) flagsChanged:(NSEvent *)event;
-(void) scrollWheel:(NSEvent *)theEvent;
-(void) setRobloxView:(class RobloxView *)rbxview;
-(void) setAppDelegate:(NSResponder *)appDelegate;
-(BOOL) cursorInViewBounds;
-(void) showCursor;
-(void) hideCursor;
-(void) toggleFullScreen;
-(BOOL) inFullScreenMode;
-(void) viewDidEndLiveResize;
-(void) finishFullScreenChange;
- (float) titleBarHeight;
- (void) setCursorPositionToVirtualPositionAndShow;
- (BOOL) isMouseOverVisibleWindow;
@end
+836
View File
@@ -0,0 +1,836 @@
//
// RobloxOgreView.m
// MacClient
//
// Created by Tony on 12/13/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "RobloxOgreView.h"
#import "RobloxView.h"
#import "RobloxPlayerAppDelegate.h"
#import "RBXWindow.h"
#import <ApplicationServices/ApplicationServices.h>
#import <Carbon/Carbon.h>
#include "G3D/G3DMath.h"
#include "v8datamodel/GameBasicSettings.h"
#define MOUSE_OFFSCREEN_POSITION CGPointMake(-5000,-5000)
#define TRACKING_WIDTH_OFFSET 5.0f
DYNAMIC_FASTFLAGVARIABLE(MiddleMouseButtonEvent, true)
@implementation RobloxOgreView
@synthesize cursorHidden;
@synthesize controlKeyWasDown;
@synthesize fullScreen;
- (id)initWithFrame:(NSRect)f;
{
if (self = [super initWithFrame:f])
{
robloxView = nil;
appDelegate = nil;
cursorHidden = NO;
controlKeyWasDown = NO;
fullScreen = NO;
virtualMousePosition = MOUSE_OFFSCREEN_POSITION;
[self updateTrackingAreas];
[self setPostsBoundsChangedNotifications: YES];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidBecomeKey:)
name:NSWindowDidBecomeKeyNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidResignKey:)
name:NSWindowDidResignKeyNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(frameChangedNotification:)
name:NSViewFrameDidChangeNotification
object:nil];
}
return self;
}
// returns the default titlebar height for OSX applications
- (float) titleBarHeight
{
NSRect frame = NSMakeRect (0, 0, 100, 100);
NSRect contentRect = [NSWindow contentRectForFrameRect: frame
styleMask: NSTitledWindowMask];
return (frame.size.height - contentRect.size.height);
}
-(void)updateTrackingAreas
{
[super updateTrackingAreas];
if(trackingArea)
{
[self removeTrackingArea:trackingArea];
[trackingArea release];
}
// TRACKING_WIDTH_OFFSET allows the mouse to enter a few pixels on each side of the application, otherwise mouse can get
// trapped at a window resizing state, which can cause issues with tracking mouse events
NSRect screenBounds = [self bounds];
screenBounds = NSMakeRect(screenBounds.origin.x + TRACKING_WIDTH_OFFSET,screenBounds.origin.y,screenBounds.size.width - (TRACKING_WIDTH_OFFSET * 2.0f),screenBounds.size.height);
trackingArea = [ [NSTrackingArea alloc] initWithRect:screenBounds
options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
mouseLocation = [self convertPoint: mouseLocation
fromView: nil];
if (NSPointInRect(mouseLocation, [self bounds]))
[self mouseEntered: nil];
else
[self mouseExited: nil];
}
- (void) updateBounds
{
NSRect bounds = [self bounds];
if (robloxView)
robloxView->setBounds(bounds.size.width, bounds.size.height);
}
- (void) frameChangedNotification: (NSNotification *) notification
{
[self updateBounds];
}
inline void flipY(RobloxOgreView *view, NSPoint &pt)
{
BOOL flipped = [view isFlipped];
if (!flipped)
{
NSRect bounds = [view bounds];
CGFloat height = bounds.size.height;
pt.y = height - pt.y;
}
}
-(void) setCursorPositionToVirtualPositionAndShow
{
if(!CGPointEqualToPoint(virtualMousePosition, MOUSE_OFFSCREEN_POSITION))
{
NSRect usableScreenRect = [[NSScreen mainScreen] frame];
NSPoint outsidePoint = NSMakePoint(virtualMousePosition.x + self.window.frame.origin.x,
virtualMousePosition.y + usableScreenRect.size.height - self.window.frame.origin.y - self.window.frame.size.height + [self titleBarHeight]);
[self showCursor];
// this code stops the "freezing" of 250 ms when warping cursor outside of Roblox window
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventSourceSetLocalEventsSuppressionInterval(source, 0.0);
CGAssociateMouseAndMouseCursorPosition(0);
CGWarpMouseCursorPosition(CGPointMake(outsidePoint.x,outsidePoint.y));
CGAssociateMouseAndMouseCursorPosition(1);
CFRelease(source);
}
else
[self showCursor];
}
-(void) setVirtualCursorPos:(CGPoint) newPos
{
if(cursorHidden)
{
virtualMousePosition = newPos;
// check to see if we have gone out of bounds, if so we need to turn off software rendering of mouse
if( ![self inFullScreenMode] && !NSPointInRect( NSMakePoint(virtualMousePosition.x,virtualMousePosition.y), [self bounds]) )
[self setCursorPositionToVirtualPositionAndShow];
}
}
-(CGPoint) getVirtualCursorPos
{
return virtualMousePosition;
}
-(BOOL) cursorInViewBounds
{
if( [self inFullScreenMode] )
return YES;
NSPoint screenPos = NSMakePoint(virtualMousePosition.x,virtualMousePosition.y);
//NSLog(@"screenPos is (%f,%f), bounds are %@",virtualMousePosition.x,virtualMousePosition.y, NSStringFromRect( [self bounds] ) );
return NSPointInRect(screenPos, [self bounds]);
}
- (void) hideCursor
{
if (cursorHidden)
return;
[NSCursor hide];
CGAssociateMouseAndMouseCursorPosition(false);
cursorHidden = YES;
}
- (void) showCursor
{
if (!cursorHidden)
return;
CGAssociateMouseAndMouseCursorPosition(true);
[NSCursor unhide];
virtualMousePosition = MOUSE_OFFSCREEN_POSITION; // kinda hack: put sw cursor offscreen
cursorHidden = NO;
}
-(void) finishFullScreenChange
{
// Get our new dimensions, others will need them
[self updateBounds];
// We don't seem to get enter/exit events after entering/exiting full screen
if (![self cursorInViewBounds])
[self mouseExited:nil];
[self.window makeFirstResponder:self];
}
- (void) toggleFullScreen
{
fullScreen = !fullScreen;
if (fullScreen)
{
NSWindow* wnd = [self window];
nonFullScreenWindowLevel = wnd.level;
nonFullScreenRect = wnd.frame;
dispatch_async(dispatch_get_main_queue(),^{
[wnd setStyleMask:NSBorderlessWindowMask];
[wnd setFrame:[NSScreen mainScreen].frame display:YES];
[wnd setLevel:NSStatusWindowLevel];
[self finishFullScreenChange];
});
}
else
{
dispatch_async(dispatch_get_main_queue(),^{
NSWindow *wnd = [self window];
[wnd setStyleMask:NSResizableWindowMask | NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask];
[wnd setFrame:nonFullScreenRect display:YES];
[wnd setLevel:nonFullScreenWindowLevel];
[self finishFullScreenChange];
});
}
}
- (BOOL) inFullScreenMode
{
return fullScreen;
}
- (void)windowDidResignKey:(NSNotification *)notification
{
NSWindow* keyWindow = (NSWindow*)[notification object];
if(keyWindow == [self window])
[self resignFirstResponder];
else
[self.window makeFirstResponder:nil];
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
NSWindow* keyWindow = (NSWindow*)[notification object];
BOOL firstResponderIsMainWindow = [[keyWindow firstResponder] isMemberOfClass:[RBXWindow class]];
if(keyWindow == [self window] || firstResponderIsMainWindow)
[self becomeFirstResponder];
}
// Event handlers
- (BOOL)becomeFirstResponder
{
if (robloxView)
robloxView->handleFocus(true);
if(!cursorHidden)
{
NSPoint loc = [NSEvent mouseLocation];
NSRect usableScreenRect = [[[NSScreen screens] objectAtIndex:0] visibleFrame];
loc.y = usableScreenRect.size.height - loc.y - [self titleBarHeight];
NSRect localWindowRect = [self bounds];
NSRect localWindowInAbsolute = NSMakeRect(_window.frame.origin.x, usableScreenRect.size.height - _window.frame.origin.y - _window.frame.size.height,
localWindowRect.size.width, localWindowRect.size.height);
if( NSPointInRect(loc,localWindowInAbsolute) )
{
[self hideCursor];
virtualMousePosition = CGPointMake(loc.x - localWindowInAbsolute.origin.x, loc.y - localWindowInAbsolute.origin.y);
if(robloxView)
robloxView->handleMouseInside(true);
}
}
return YES;
}
- (BOOL)resignFirstResponder
{
if (robloxView)
robloxView->handleFocus(false);
[self setCursorPositionToVirtualPositionAndShow];
return YES;
}
-(void) setRobloxView:(RobloxView *)rbxview
{
robloxView = rbxview;
if (robloxView)
{
// Tell Roblox window how big it is early
[self updateBounds];
}
else
{
// Clean up Ogre stuff
//[self setOgreWindow:nil];
// make sure cursor shows up etc
[self showCursor];
}
}
-(void) setAppDelegate:(NSResponder *)appDel
{
self->appDelegate = appDel;
if (RBX::GameBasicSettings::singleton().getFullScreen() && !fullScreen)
{
[self toggleFullScreen];
}
}
- (void)viewDidEndLiveResize
{
[self updateBounds];
}
static RBX::KeyCode getModifierKeyCode(unsigned short keyCode)
{
if(keyCode == kVK_CapsLock)
{
return RBX::SDLK_CAPSLOCK;
}
if (keyCode == kVK_Shift || keyCode == kVK_RightShift)
{
return RBX::SDLK_LSHIFT;
}
if (keyCode == kVK_Control || keyCode == kVK_RightControl)
{
return RBX::SDLK_LCTRL;
}
if (keyCode == kVK_Option || keyCode == kVK_RightOption)
{
return RBX::SDLK_LALT;
}
return RBX::SDLK_UNKNOWN;
}
static RBX::KeyCode keyCodeTOUIKeyCode(unsigned int c, unsigned int keyCode, NSUInteger modifiers)
{
// Look for keypad-related, translate (somehow) to numeric keypad codes
// right now it seems to always behave as if Num Lock is on, sending the number
// ASCII chars instead of the arrows etc.
if (modifiers & NSNumericPadKeyMask)
{
}
if (c == 127)
return RBX::SDLK_BACKSPACE;
// try ASCII table. RBX keycodes in ASCII range are mapped exactly
if (c <= 127)
{
// for A-Z we'll need the lower case versions
if (isalpha(c))
{
c = tolower(c);
}
//NSLog([NSString stringWithFormat:@"keyCodeTOUIKeyCode %d", c]);
return (RBX::KeyCode) c;
}
RBX::KeyCode rbxKey = RBX::SDLK_UNKNOWN;
// Now try the gnarlier key codes. The arrows, function keys etc. are mapped to unicode chars in the higher range
switch(c)
{
case NSUpArrowFunctionKey :
rbxKey = RBX::SDLK_UP;
break;
case NSDownArrowFunctionKey :
rbxKey = RBX::SDLK_DOWN;
break;
case NSLeftArrowFunctionKey :
rbxKey = RBX::SDLK_LEFT;
break;
case NSRightArrowFunctionKey :
rbxKey = RBX::SDLK_RIGHT;
break;
case NSF1FunctionKey :
rbxKey = RBX::SDLK_F1;
break;
case NSF2FunctionKey :
rbxKey = RBX::SDLK_F2;
break;
case NSF3FunctionKey :
rbxKey = RBX::SDLK_F3;
break;
case NSF4FunctionKey :
rbxKey = RBX::SDLK_F4;
break;
case NSF5FunctionKey :
rbxKey = RBX::SDLK_F5;
break;
case NSF6FunctionKey :
rbxKey = RBX::SDLK_F6;
break;
case NSF7FunctionKey :
rbxKey = RBX::SDLK_F7;
break;
case NSF8FunctionKey :
rbxKey = RBX::SDLK_F8;
break;
case NSF9FunctionKey :
rbxKey = RBX::SDLK_F9;
break;
case NSF10FunctionKey :
rbxKey = RBX::SDLK_F10;
break;
case NSF11FunctionKey :
rbxKey = RBX::SDLK_F11;
break;
case NSF12FunctionKey :
rbxKey = RBX::SDLK_F12;
break;
case NSF13FunctionKey :
rbxKey = RBX::SDLK_F13;
break;
case NSF14FunctionKey :
rbxKey = RBX::SDLK_F14;
break;
case NSF15FunctionKey :
rbxKey = RBX::SDLK_F15;
break;
case NSInsertFunctionKey :
rbxKey = RBX::SDLK_INSERT;
break;
case NSDeleteFunctionKey :
rbxKey = RBX::SDLK_DELETE;
break;
case NSHomeFunctionKey :
rbxKey = RBX::SDLK_HOME;
break;
case NSEndFunctionKey :
rbxKey = RBX::SDLK_END;
break;
case NSPageUpFunctionKey :
rbxKey = RBX::SDLK_PAGEUP;
break;
case NSPageDownFunctionKey :
rbxKey = RBX::SDLK_PAGEDOWN;
break;
default :
rbxKey = RBX::SDLK_UNKNOWN;
break;
}
return rbxKey;
}
static bool modifierKeyPressed(unsigned short keyCode, NSUInteger modifiers)
{
if ( (keyCode == kVK_CapsLock) && (modifiers & NSAlphaShiftKeyMask) )
return true;
if ( (keyCode == kVK_Shift) && (modifiers & NSShiftKeyMask) )
return true;
if ( (keyCode == kVK_Control) && (modifiers & NSControlKeyMask) )
return true;
if ( (keyCode == kVK_Option) && (modifiers & NSAlternateKeyMask) )
return true;
return false;
}
static RBX::ModCode modifiersToUIModCode(NSUInteger modifiers)
{
unsigned int modCode = 0;
if( modifiers & NSAlphaShiftKeyMask )
modCode = modCode | RBX::KMOD_CAPS;
if (modifiers & NSShiftKeyMask)
modCode = modCode | RBX::KMOD_LSHIFT;
if (modifiers & NSControlKeyMask)
modCode = modCode | RBX::KMOD_LCTRL;
if (modifiers & NSAlternateKeyMask)
modCode = modCode | RBX::KMOD_LALT;
if (modifiers & NSCommandKeyMask)
modCode = modCode | RBX::KMOD_LMETA;
return (RBX::ModCode) modCode;
}
- (void)mouseEntered:(NSEvent *)theEvent
{
if (robloxView && [[NSApplication sharedApplication] isActive] && self.window.isKeyWindow)
{
[self hideCursor];
NSPoint loc;
if (theEvent)
loc = [theEvent locationInWindow];
else
loc = [[self window] mouseLocationOutsideOfEventStream];
NSPoint myloc = [self convertPoint:loc fromView:nil];
flipY(self, myloc);
virtualMousePosition = CGPointMake(myloc.x, myloc.y);
robloxView->handleMouseInside(true);
}
}
- (void)mouseExited:(NSEvent *)theEvent
{
if (robloxView && [[NSApplication sharedApplication] isActive])
{
//NSLog(@"mouseExited inside");
[self showCursor];
robloxView->handleMouseInside(false);
}
}
-(BOOL) isMouseOverVisibleWindow
{
NSPoint absMousePos = NSMakePoint(virtualMousePosition.x + self.window.frame.origin.x,
virtualMousePosition.y + self.window.frame.origin.y);
NSArray* windows = [[NSApplication sharedApplication] windows];
for(NSWindow* aWindow in windows)
{
if (aWindow != self.window &&
[aWindow isVisible] &&
aWindow.level >= self.window.level &&
NSPointInRect(absMousePos, aWindow.frame))
{
[aWindow makeKeyAndOrderFront:self];
[aWindow makeFirstResponder:nil];
return YES;
}
}
return NO;
}
-(void)mouseMoved:(NSEvent *)event
{
BOOL firstResponderIsMainWindow = [[self.window firstResponder] isMemberOfClass:[RBXWindow class]];
if (robloxView &&
[[NSApplication sharedApplication] isActive] &&
[[NSApplication sharedApplication] keyWindow] == self.window &&
([self.window firstResponder] == self || firstResponderIsMainWindow) &&
[self cursorInViewBounds])
{
// if we can see some other one of our windows open, give it focus
if ([self isMouseOverVisibleWindow])
return;
[[self window] makeFirstResponder:self];
NSPoint loc = [event locationInWindow];
NSPoint myloc = [self convertPoint:loc fromView:nil];
flipY(self, myloc);
if(!cursorHidden)
[self hideCursor];
// NSLog(@"mouseMoved at %@ (window) %@ (view) (%f,%f) (delta)", NSStringFromPoint(loc), NSStringFromPoint(myloc),[event deltaX],[event deltaY]);
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_MOVE, [event deltaX], [event deltaY], modifiersToUIModCode([event modifierFlags]));
}
}
-(void)mouseDown:(NSEvent *)event
{
if (event.modifierFlags & NSControlKeyMask)
{
controlKeyWasDown = YES;
return [self rightMouseDown:event];
}
NSPoint loc = [event locationInWindow];
NSPoint myloc = [self convertPoint:loc fromView:nil];
// flip mouse coords from Y-up to Y-down
flipY(self, myloc);
if(!cursorHidden)
{
[self hideCursor];
virtualMousePosition = CGPointMake(myloc.x, myloc.y);
robloxView->handleMouseInside(true);
}
//NSLog(@"mouseDown at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_LEFT_BUTTON_DOWN, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
}
-(void)mouseUp:(NSEvent *)event
{
if (event.modifierFlags & NSControlKeyMask || controlKeyWasDown)
{
controlKeyWasDown = false;
return [self rightMouseUp:event];
}
NSPoint loc = [event locationInWindow];
NSPoint myloc = [self convertPoint:loc fromView:nil];
// NSLog(@"mouseUp at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
// possibly flip mouse coords from Y-up to Y-down
flipY(self, myloc);
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_LEFT_BUTTON_UP, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
}
-(void)mouseDragged:(NSEvent *)event
{
if (event.modifierFlags & NSControlKeyMask || controlKeyWasDown)
return [self rightMouseDragged:event];
NSPoint loc = [event locationInWindow];
NSPoint myloc = [self convertPoint:loc fromView:nil];
//NSLog(@"mouseDragged at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
// possibly flip mouse coords from Y-up to Y-down
flipY(self, myloc);
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_MOVE, [event deltaX], [event deltaY], modifiersToUIModCode([event modifierFlags]));
}
-(void)rightMouseDown:(NSEvent *)event
{
[[self window] makeFirstResponder:self];
NSPoint loc = [event locationInWindow];
NSPoint myloc = [self convertPoint:loc fromView:nil];
//NSLog(@"mouseDown at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
// possibly flip mouse coords from Y-up to Y-down
flipY(self, myloc);
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_RIGHT_BUTTON_DOWN, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
}
-(void)rightMouseDragged:(NSEvent *)event
{
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_MOVE, [event deltaX], [event deltaY], modifiersToUIModCode([event modifierFlags]));
}
-(void)rightMouseUp:(NSEvent *)event
{
NSPoint loc = [event locationInWindow];
NSPoint myloc = [self convertPoint:loc fromView:nil];
// NSLog(@"mouseUp at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
// possibly flip mouse coords from Y-up to Y-down
flipY(self, myloc);
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_RIGHT_BUTTON_UP, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
}
-(void)otherMouseDown:(NSEvent *)event
{
if (DFFlag::MiddleMouseButtonEvent)
{
if ([event buttonNumber] == (NSInteger)2)
{
[[self window] makeFirstResponder:self];
NSPoint loc = [event locationInWindow];
NSPoint myloc = [self convertPoint:loc fromView:nil];
//NSLog(@"mouseDown at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
// possibly flip mouse coords from Y-up to Y-down
flipY(self, myloc);
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_MIDDLE_BUTTON_DOWN, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
}
}
}
-(void)otherMouseDragged:(NSEvent *)event
{
if (DFFlag::MiddleMouseButtonEvent)
{
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_MOVE, [event deltaX], [event deltaY], modifiersToUIModCode([event modifierFlags]));
}
}
-(void)otherMouseUp:(NSEvent *)event
{
if (DFFlag::MiddleMouseButtonEvent)
{
if ([event buttonNumber] == (NSInteger)2)
{
NSPoint loc = [event locationInWindow];
NSPoint myloc = [self convertPoint:loc fromView:nil];
// NSLog(@"mouseUp at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
// possibly flip mouse coords from Y-up to Y-down
flipY(self, myloc);
if (robloxView)
robloxView->handleMouse(RobloxView::MOUSE_MIDDLE_BUTTON_UP, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
}
}
}
-(void)keyDown:(NSEvent *)event
{
if( ![event isARepeat])
{
NSString *chars = [event characters];
unsigned short keyCode = [event keyCode];
NSUInteger modifiers = [event modifierFlags];
if (robloxView)
{
NSUInteger len = [chars length];
if (len > 0)
{
RBX::KeyCode rbxKey = keyCodeTOUIKeyCode([chars characterAtIndex:0], keyCode, modifiers);
robloxView->handleKey(RobloxView::KEY_DOWN, rbxKey, modifiersToUIModCode(modifiers));
}
}
}
}
-(void)flagsChanged:(NSEvent *)event
{
RBX::KeyCode modKeyCode = getModifierKeyCode([event keyCode]);
RBX::ModCode UIModCode = modifiersToUIModCode([event modifierFlags]);
if( modifierKeyPressed([event keyCode], [event modifierFlags]) )
robloxView->handleKey(RobloxView::KEY_DOWN, modKeyCode, UIModCode);
else
robloxView->handleKey(RobloxView::KEY_UP, modKeyCode, UIModCode);
}
-(void)keyUp:(NSEvent *)event
{
if(![event isARepeat])
{
NSString *chars = [event characters];
NSUInteger modifiers = [event modifierFlags];
if (robloxView)
{
NSUInteger len = [chars length];
if (len > 0)
{
RBX::KeyCode keyCode = keyCodeTOUIKeyCode([chars characterAtIndex:0], [event keyCode], modifiers);
robloxView->handleKey(RobloxView::KEY_UP, keyCode, modifiersToUIModCode(modifiers));
}
}
}
}
#define RBXDELTAY 120.f
-(void)scrollWheel:(NSEvent *)theEvent
{
// Get scroll info
CGFloat deltaY = [theEvent deltaY];
CGFloat deltaX = [theEvent deltaX];
// NSLog(@"Got a scroll deltaX:%g deltaY:%g deltaZ:%g", deltaX, deltaY, deltaZ);
if (robloxView)
{
//NSLog (@"Transducing deltaY to %g", rbxDeltaY);
if (deltaY)
{
// Convert (bizarre) Cocoa deltaY values to (even more bizarre) Windows values
float rbxDeltaY = deltaY > 0.f ? RBXDELTAY : -RBXDELTAY;
robloxView->handleScrollWheel(rbxDeltaY, virtualMousePosition.x, virtualMousePosition.y);
}
// Horizontal scrolling reports as Delta in X-Axis
else if (deltaX)
{
float rbxDeltaX = deltaX > 0.f ? RBXDELTAY : -RBXDELTAY;
robloxView->handleScrollWheel(rbxDeltaX, virtualMousePosition.x, virtualMousePosition.y);
}
}
}
@end
+4
View File
@@ -0,0 +1,4 @@
// RobloxPlayer.cpp : Defines the entry point for the console application.
//
// NOTE: Contents of this file have been gutted and moved to various other files and classes. Let's remove it from source control soon -- TP
+91
View File
@@ -0,0 +1,91 @@
//
// SimplePlayerMacAppDelegate.h
// SimplePlayerMac
//
// Created by Tony on 10/26/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#pragma once
#import <Cocoa/Cocoa.h>
#import <Breakpad.h>
#import "RobloxOgreView.h"
#include "rbx/signal.h"
#import "RbxWebView.h"
#import "RBXWindow.h"
class RobloxView;
namespace RBX
{
class DataModel;
}
@interface RobloxPlayerAppDelegate : NSResponder <NSWindowDelegate>
{
RBXWindow *window;
NSView *mainView;
RbxWebView *rbxWebView;
RobloxOgreView *ogreView;
RobloxView *robloxView;
NSMenu *mainMenu;
BOOL running;
BOOL quitOnLeave;
RobloxAppShutdownCode shutdownCode;
BreakpadRef breakpad;
rbx::signals::scoped_connection openUrlConnection;
rbx::signals::scoped_connection closeUrlConnection;
RobloxGameType gameType;
}
-(id) init;
- (BOOL) checkUpdater:(BOOL) showUpdateOptionsDialog;
-(void)marshallFunction:(NSEvent *)evt;
-(void)addLogFile:(NSString *)file;
-(void)addBreakPadKeyValue:(NSString *)breakPadKey withValue:(NSString *)value;
-(void)addDbgInfoToBreakPad;
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification;
-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication;
-(void)applicationWillTerminate:(NSNotification *)aNotification;
-(void)windowWillClose:(NSNotification *)notification;
-(void)ShutdownDataModel;
-(void)leaveGame;
-(void)handleLeaveGame;
-(BOOL)requestShutdownClient:(RobloxAppShutdownCode)code;
-(void)handleShutdownClient;
-(void)handleToggleFullScreen;
-(bool)inFullScreenMode;
-(void)teleport:(NSString *)ticket withAuthentication:(NSString *)url withScript:(NSString *)script;
-(void)StartGame:(NSString *)ticket withAuthentication:(NSString *)url withScript:(NSString *)script;
bool requestPlaceInfo(int testPlaceID, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl);
RequestPlaceInfoResult requestPlaceInfo(const std::string& placeLauncherUrl, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl);
-(void) openUrlWindow:(std::string) url;
- (IBAction)crash:(id)sender;
-(void) setupGameServices;
-(void) setupDataModelServices:(RBX::DataModel*) dataModel;
+(void) reportRenderViewInitError:(const char *)message;
@property (assign) IBOutlet RBXWindow *window;
@property (assign) IBOutlet NSView *mainView;
@property (readwrite, assign) RobloxView *robloxView;
@property (assign) IBOutlet RobloxOgreView *ogreView;
@property (assign) IBOutlet NSMenu *mainMenu;
@end
File diff suppressed because it is too large Load Diff
+749
View File
@@ -0,0 +1,749 @@
#include "RobloxView.h"
#include "Roblox.h"
#include "GfxBase/ViewBase.h"
#include "v8datamodel/datamodel.h"
#include "v8datamodel/workspace.h"
#include "v8datamodel/camera.h"
#include "v8datamodel/game.h"
#include "v8datamodel/InputObject.h"
#include "v8datamodel/GuiService.h"
#include "FunctionMarshaller.h"
#include "Util/StandardOut.h"
#include "Util/FileSystem.h"
#include "rbx/SystemUtil.h"
#include "rbx/Tasks/Coordinator.h"
#include "UserInput.h"
#include "Util/IMetric.h"
#include "Util/Object.h"
#include "GfxBase/RenderSettings.h"
#include "GfxBase/FrameRateManager.h"
#include "v8datamodel/BaseRenderJob.h"
#include "v8datamodel/UserController.h"
#include "v8datamodel/UserInputService.h"
#include "Util/Statistics.h"
#include "v8datamodel/ContentProvider.h"
#include "script/ScriptContext.h"
#include "v8xml/Serializer.h"
#include "rbx/CEvent.h"
#include "GameVerbs.h"
#include "Network/Players.h"
#include "../ClientBase/RenderSettingsItem.h"
#include <boost/iostreams/copy.hpp>
#include "V8DataModel/GameBasicSettings.h"
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
// This job calls ViewBase::render(), which needs to be done exclusive to the DataModel.
// This is why it has the RBX::DataModelJob::Render enum, which prevents concurrent writes to DataModel.
// It also needs to run in the view's thread for OpenGL
// TODO: Can Ogre be modified to not require the thread?
class RobloxView::RenderJob : public RBX::BaseRenderJob
, public RBX::IMetric
{
RBX::FunctionMarshaller* marshaller;
weak_ptr<RBX::DataModel> dataModel;
RBX::ViewBase* view;
RBX::CEvent renderEvent;
public:
RenderJob(RBX::ViewBase* view, RBX::FunctionMarshaller* marshaller, shared_ptr<RBX::DataModel> dataModel)
:RBX::BaseRenderJob(CRenderSettingsItem::singleton().getMinFrameRate(), CRenderSettingsItem::singleton().getMaxFrameRate(), dataModel),
view(view),
dataModel(dataModel),
marshaller(marshaller),
renderEvent(false)
{
cyclicExecutive = true;
}
RBX::Time::Interval sleepTime(const Stats& stats)
{
if (isAwake)
return computeStandardSleepTime(stats, CRenderSettingsItem::singleton().getMaxFrameRate());
else
return RBX::Time::Interval::max();
}
static void scheduleRenderPerform(const weak_ptr<RenderJob>& selfWeak, ViewBase* view, double timeJobStart)
{
if (shared_ptr<RenderJob> self = selfWeak.lock())
{
view->renderPerform(timeJobStart);
self->wake();
}
}
void doDataModelRenderStep(DataModel* dm, const float secondsElapsed)
{
dm->renderStep(secondsElapsed);
isAwake = false;
marshaller->Execute(boost::bind(&RBX::ViewBase::renderPrepare, view, this), &renderEvent);
}
virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
{
DataModel* dm = view->getDataModel();
if (!dm)
return RBX::TaskScheduler::Done;
double timeJobStart = RBX::Time::nowFastSec();
try
{
const float secondsElapsed = view->getFrameRateManager()->GetFrameTimeStats().getLatest() / 1000.f;
lastRenderTime = RBX::Time::now<RBX::Time::Fast>();
// TODO: Can we fix Ogre so that it can be called from this thread, rather than marshalled?
RBX::DataModel::scoped_write_request request(dm);
doDataModelRenderStep(dm, secondsElapsed);
marshaller->Submit(boost::bind(&scheduleRenderPerform, weak_from(this), view, timeJobStart));
}
catch (std::exception& e)
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e);
}
return RBX::TaskScheduler::Stepped;
}
void abortRender()
{
renderEvent.Set();
}
// IMetric
/*override*/ double getMetricValue(const std::string& metric) const
{
RBX::FrameRateManager* frm = view ? view->getFrameRateManager() : 0;
if (metric == "Render FPS") {
return averageStepsPerSecond();
}
if (metric == "Render Duty") {
return averageDutyCycle();
}
if (metric == "Render Nominal FPS") {
return frm ? 1000.0 / frm->GetRenderTimeAverage() : 0.0;
}
if(metric == "Delta Between Renders")
{
return view->getMetricValue(metric);
}
else if (metric == "Total Render")
{
return view->getMetricValue(metric);
}
else if (metric == "Present Time")
{
return view->getMetricValue(metric);
}
else if (metric == "GPU Delay")
{
return view->getMetricValue(metric);
}
else if (metric == "Video Memory")
{
return (double)RBX::SystemUtil::getVideoMemory();
}
return 0.0;
}
/*override*/ std::string getMetric(const std::string& metric) const
{
if (! view ) {
return "No View";
}
if (metric == "Graphics Mode") {
return ""; // RBX::Reflection::EnumDesc<CRenderSettings::GraphicsMode>::singleton().convertToString(graphicsMode);
}
RBX::FrameRateManager* frm = view ? view->getFrameRateManager() : 0;
if (metric == "FRM") {
return (frm && frm->IsBlockCullingEnabled()) ? "On" : "Off";
}
if (metric == "Anti-Aliasing") {
return (frm && frm->getAntialiasingMode() == RBX::CRenderSettings::AntialiasingOn) ? "On" : "Off";
}
RBXASSERT(0);
return "";
}
};
class RobloxView::UserInputJob : public RBX::DataModelJob
{
RobloxView* wnd;
RBX::DataModel* dataModel;
public:
UserInputJob(RobloxView* wnd, shared_ptr<RBX::DataModel> dataModel)
:RBX::DataModelJob("UserInput", RBX::DataModelJob::Write, true, dataModel, RBX::Time::Interval(0)),
wnd(wnd),
dataModel(dataModel.get())
{
}
RBX::Time::Interval sleepTime(const Stats& stats)
{
return computeStandardSleepTime(stats, 60);
}
virtual Job::Error error(const Stats& stats)
{
Job::Error result = computeStandardError(stats, 60);
return result;
}
virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
{
RBX::DataModel::scoped_write_request request(dataModel);
if(wnd)
wnd->processInput();
return RBX::TaskScheduler::Stepped;
}
};
static RBX::InputObject::UserInputType viewEventToUserInputType(RobloxView::EventType event)
{
switch (event)
{
case RobloxView::MOUSE_MOVE:
return RBX::InputObject::TYPE_MOUSEMOVEMENT;
case RobloxView::MOUSE_LEFT_BUTTON_DOWN:
case RobloxView::MOUSE_LEFT_BUTTON_UP: // intentional fall thru
return RBX::InputObject::TYPE_MOUSEBUTTON1;
case RobloxView::MOUSE_RIGHT_BUTTON_DOWN:
case RobloxView::MOUSE_RIGHT_BUTTON_UP: // intentional fall thru
return RBX::InputObject::TYPE_MOUSEBUTTON2;
case RobloxView::MOUSE_MIDDLE_BUTTON_DOWN:
case RobloxView::MOUSE_MIDDLE_BUTTON_UP: // intentional fall thru
return RBX::InputObject::TYPE_MOUSEBUTTON3;
case RobloxView::KEY_UP:
case RobloxView::KEY_DOWN: // intentional fall thru
return RBX::InputObject::TYPE_KEYBOARD;
default:
return RBX::InputObject::TYPE_NONE;
}
}
static RBX::InputObject::UserInputState viewEventToUserInputState(RobloxView::EventType event)
{
switch (event)
{
case RobloxView::MOUSE_MOVE:
return RBX::InputObject::INPUT_STATE_CHANGE;
case RobloxView::MOUSE_LEFT_BUTTON_DOWN:
case RobloxView::MOUSE_RIGHT_BUTTON_DOWN:
case RobloxView::MOUSE_MIDDLE_BUTTON_DOWN:
case RobloxView::KEY_DOWN: // intentional fall thru
return RBX::InputObject::INPUT_STATE_BEGIN;
case RobloxView::MOUSE_LEFT_BUTTON_UP:
case RobloxView::MOUSE_RIGHT_BUTTON_UP:
case RobloxView::MOUSE_MIDDLE_BUTTON_UP:
case RobloxView::KEY_UP: // intentional fall thru
return RBX::InputObject::INPUT_STATE_END;
default:
return RBX::InputObject::INPUT_STATE_NONE;
}
}
void RobloxView::processInput()
{
/*if (userInput && userInput.get())
if(userInput->getWrapMode() == RBX::UserInputBase::WRAP_HYBRID)
userInput->ProcessUserInputMessage(RBX::UIEvent::NO_EVENT,0,0);*/
}
// Request a shutdown of the client
Boolean RobloxView::requestShutdownClient()
{
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
{
// give scripts a deadline to finish
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
if (ScriptContext* scriptContext = game->getDataModel()->find<ScriptContext>())
scriptContext->setTimeout(FLog::PlayerShutdownLuaTimeoutSeconds);
DataModel::RequestShutdownResult requestResult = dataModel->requestShutdown();
return requestResult == DataModel::CLOSE_REQUEST_HANDLED;
}
return true;
}
void RobloxView::handleUserMessage(EventType event, unsigned int wParam, unsigned int lParam)
{
if (userInput)
{
userInput->PostUserInputMessage(viewEventToUserInputType(event), viewEventToUserInputState(event), wParam, lParam);
}
}
void RobloxView::handleFocus(bool focus)
{
if (userInput)
{
userInput->PostUserInputMessage(RBX::InputObject::TYPE_FOCUS, focus ? RBX::InputObject::INPUT_STATE_BEGIN : RBX::InputObject::INPUT_STATE_END, 0, 0);
}
}
void RobloxView::handleMouseInside(bool inside)
{
if (userInput)
{
if (inside)
userInput->onMouseInside();
else
userInput->onMouseLeave();
}
}
void RobloxView::handleMouse(EventType event, int x, int y, unsigned int modifiers)
{
if (userInput)
{
userInput->PostUserInputMessage(viewEventToUserInputType(event), viewEventToUserInputState(event), modifiers, MAKEXYLPARAM((uint) x, (uint) y));
}
}
void RobloxView::handleKey(EventType event, RBX::KeyCode keyCode, RBX::ModCode modifiers)
{
if (userInput)
{
userInput->PostUserInputMessage(viewEventToUserInputType(event), viewEventToUserInputState(event), keyCode, modifiers);
}
}
void RobloxView::handleScrollWheel(float delta, int x, int y)
{
if (userInput)
{
userInput->PostUserInputMessage(RBX::InputObject::TYPE_MOUSEWHEEL, RBX::InputObject::INPUT_STATE_CHANGE, delta, MAKEXYLPARAM((uint) x, (uint) y));
}
}
void RobloxView::leaveGame()
{
marshaller->Submit(boost::bind(&RobloxView::handleLeaveGame, this));
}
void RobloxView::handleLeaveGame()
{
Roblox::handleLeaveGame(appWindow);
}
void RobloxView::shutdownClient()
{
marshaller->Submit(boost::bind(&RobloxView::handleShutdownClient, this));
}
void RobloxView::handleShutdownClient()
{
Roblox::handleShutdownClient(appWindow);
}
void RobloxView::toggleFullScreen()
{
if(getDataModel())
getDataModel()->submitTask(boost::bind(&RobloxView::handleToggleFullScreen, this), RBX::DataModelJob::Write);
}
void RobloxView::handleToggleFullScreen()
{
Roblox::handleToggleFullScreen(appWindow);
RBX::GameBasicSettings::singleton().setFullScreen(Roblox::inFullScreenMode(appWindow));
}
extern std::string macBundlePath();
static RBX::ViewBase* createGameWindow(void *wnd)
{
// static initialization:
static boost::once_flag flag = BOOST_ONCE_INIT;
boost::call_once(&RBX::ViewBase::InitPluginModules, flag);
RBX::OSContext context;
context.hWnd = wnd;
context.width = 800;
context.height = 600;
RBX::CRenderSettings& settings = CRenderSettingsItem::singleton();
RBX::CRenderSettings::GraphicsMode mode = RBX::CRenderSettings::OpenGL;
RBX::ViewBase* rbxView = RBX::ViewBase::CreateView(mode, &context, &settings);
rbxView->initResources();
return rbxView;
}
class DummyVerb : public RBX::Verb {
public:
DummyVerb(VerbContainer* container, const char* name)
:Verb(container, name)
{
}
virtual bool isEnabled() const {return false;}
virtual void doIt(RBX::IDataState* dataState) {}
};
RobloxView::RobloxView(void *viewwnd, void *appwnd, boost::shared_ptr<RBX::Game> game)
:view(createGameWindow(viewwnd))
,appWindow(appwnd)
,viewWindow(viewwnd)
,userInput(new UserInput(this, game))
,game(game)
,leaveGameVerb(new LeaveGameVerb(this, game->getDataModel().get()))
,fullscreenVerb(new ToggleFullscreenVerb(this, game->getDataModel().get(), NULL))
,studioVerb(new DummyVerb(game->getDataModel().get(), "TogglePlayMode"))
,screenshotVerb(new DummyVerb(game->getDataModel().get(), "Screenshot"))
,shutdownClientVerb(new ShutdownClientVerb(this, game->getDataModel().get()))
,marshaller(RBX::FunctionMarshaller::GetWindow())
{
shared_ptr<RBX::DataModel> dataModel = game->getDataModel();
placeIDChangeConnection = dataModel->propertyChangedSignal.connect(boost::bind(&RobloxView::onPlaceIDChanged, this, _1));
// Bind to the Workspace
bindToWorkspace();
// Create the rendering jobs
renderJob = shared_ptr<RenderJob>(new RenderJob(view.get(), marshaller, dataModel));
// Create the user input job
userInputJob = shared_ptr<UserInputJob>(new UserInputJob(this, dataModel));
defineConcurrencyRules();
RBX::TaskScheduler::singleton().add(renderJob);
if (userInputJob)
{
RBX::TaskScheduler::singleton().add(userInputJob);
}
}
void RobloxView::onPlaceIDChanged(const RBX::Reflection::PropertyDescriptor* desc)
{
bool placeIDChanged = desc->name=="PlaceId";
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
if(placeIDChanged && dataModel->getPlaceID() > 0)
Roblox::addBreakPadKeyValue("Place0", dataModel->getPlaceID());
}
void RobloxView::defineConcurrencyRules()
{
{
// Force viewUpdateJob and renderJob to happen serially
boost::shared_ptr<RBX::Tasks::Coordinator> sequence(new RBX::Tasks::ExclusiveSequence());
renderJob->addCoordinator(sequence);
}
if (CRenderSettingsItem::singleton().isSynchronizedWithPhysics)
{
// Force rendering and physics to happen in lock-step
sequence.reset(new RBX::Tasks::Sequence());
renderJob->addCoordinator(sequence);
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
dataModel->create<RBX::RunService>()->getPhysicsJob()->addCoordinator(sequence);
}
}
void RobloxView::doUnbindWorkspace()
{
if(view)
view->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
}
void RobloxView::initializeInput()
{
userInput.reset(new UserInput(this, game));
if(userInput)
{
DataModel::LegacyLock lock(game->getDataModel(), DataModelJob::Write);
ControllerService* service = ServiceProvider::create<ControllerService>(game->getDataModel().get());
service->setHardwareDevice(userInput.get());
}
}
void RobloxView::stopJobs()
{
if (renderJob)
{
renderJob->abortRender();
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
RBX::TaskScheduler::singleton().removeBlocking(renderJob, callback);
}
if (userInputJob)
{
boost::function<void()> callback = boost::bind(&FunctionMarshaller::ProcessMessages, marshaller);
TaskScheduler::singleton().removeBlocking(userInputJob, callback);
}
// RenderJob is sure to be completed at this point, since removeBlocking returned - but it might have marshalled
// renderPerform asynchronously before exiting, which means that we might still have a callback that uses this view
// in the marshaller queue.
// This makes sure that all pending marshalled events are processed to avoid a use after free.
marshaller->ProcessMessages();
// All render processing is complete; it's safe to reset job pointers now
renderJob.reset();
userInputJob.reset();
}
void RobloxView::doShutdownDataModel()
{
if(game)
{
{
RBX::DataModel::LegacyLock lock(game->getDataModel(), RBX::DataModelJob::Write);
marshaller->Submit(boost::bind(&RobloxView::doUnbindWorkspace, this));
marshaller->ProcessMessages();
if(shared_ptr<DataModel> dataModel = game->getDataModel())
{
if (RBX::RunService* runService = dataModel->find<RBX::RunService>())
runService->stopTasks();
if(RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(dataModel.get()))
service->setHardwareDevice(NULL);
}
if (sequence)
{
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
if (RBX::RunService* rs = dataModel->find<RBX::RunService>())
rs->getPhysicsJob()->removeCoordinator(sequence);
}
stopJobs();
marshaller->ProcessMessages();
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
{
userInput.reset();
leaveGameVerb.reset();
fullscreenVerb.reset();
studioVerb.reset();
screenshotVerb.reset();
shutdownClientVerb.reset();
}
game->shutdown();
}
game->shutdown();
}
// marshaller has not finished unbinding workspace, wait until this is finished
if(view->getDataModel())
{
while (view->getDataModel())
{
sleep(0.016667f);
}
}
RBXASSERT(!view->getDataModel());
}
RobloxView::~RobloxView(void)
{
if (sequence)
{
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
if (RBX::RunService* rs = dataModel->find<RBX::RunService>())
rs->getPhysicsJob()->removeCoordinator(sequence);
}
stopJobs();
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
{
RBX::DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write);
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(dataModel.get());
service->setHardwareDevice(NULL);
view->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
userInput.reset();
leaveGameVerb.reset();
fullscreenVerb.reset();
studioVerb.reset();
screenshotVerb.reset();
shutdownClientVerb.reset();
}
RBX::FunctionMarshaller::ReleaseWindow(marshaller);
// First destroy the view before closing the DataModel
view.reset();
Roblox::relinquishGame(game);
}
void RobloxView::bindToWorkspace()
{
shared_ptr<RBX::DataModel> dataModel = game->getDataModel();
if(!dataModel)
return;
// Note that this code needs to be thread-sensitive
RBX::DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write);
view->bindWorkspace(dataModel);
view->buildGui();
if (userInput)
{
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(dataModel.get());
service->setHardwareDevice(userInput.get());
}
}
void RobloxView::setBounds(unsigned int width, unsigned int height)
{
this->width = width; this->height = height;
if (view)
{
view->onResize(width, height);
}
}
static void executeScript(boost::shared_ptr<RBX::Game> game, std::string urlScript, bool isApp, bool isFromProtocolHandler)
{
RBX::Security::Impersonator impersonate(RBX::Security::COM);
std::ostringstream data;
if (RBX::ContentProvider::isUrl(urlScript))
{
RBX::DataModel::LegacyLock lock(game->getDataModel(), RBX::DataModelJob::Write);
std::auto_ptr<std::istream> stream(RBX::ServiceProvider::create<RBX::ContentProvider>(game->getDataModel().get())->getContent(RBX::ContentId(urlScript.c_str())));
boost::iostreams::copy(*stream, data);
}
else
return; // silent failure is harder to hack :)
RBX::ProtectedString verifiedSource;
try
{
verifiedSource = ProtectedString::fromTrustedSource(data.str());
ContentProvider::verifyScriptSignature(verifiedSource, true);
}
catch(std::bad_alloc&)
{
throw;
}
catch(std::exception&)
{
return;
}
shared_ptr<RBX::DataModel> dm = game->getDataModel();
if(!dm)
return;
RBX::DataModel::LegacyLock lock(dm, RBX::DataModelJob::Write);
if (dm->isClosed())
return;
if (urlScript.find("join.ashx"))
{
std::string data = verifiedSource.getSource();
int firstNewLineIndex = data.find("\r\n");
if (data[firstNewLineIndex+2] == '{')
{
// TODO: create shared enum between PC and Mac
// Values taken from SharedLauncher::LaunchMode
int launchMode = 0;
if (isFromProtocolHandler)
launchMode = 1;
game->configurePlayer(RBX::Security::COM, data.substr(firstNewLineIndex+2), launchMode);
return;
}
}
RBX::ScriptContext* context = dm->create<RBX::ScriptContext>();
context->executeInNewThread(RBX::Security::COM, verifiedSource, "Start Game");
}
boost::shared_ptr<RBX::Game> RobloxView::startGame(std::string urlScript, const bool isApp) {
boost::shared_ptr<RBX::Game> game(Roblox::getpreloadedGame(isApp));
boost::thread(boost::bind(&executeScript, game, urlScript, isApp, false));
return game;
}
RobloxView *RobloxView::start_game(void *wnd, void *appwnd, std::string urlScript, const bool isApp)
{
boost::shared_ptr<RBX::Game> game = startGame(urlScript, isApp);
return new RobloxView(wnd, appwnd, game);
}
RobloxView *RobloxView::init_game(void *wnd, void* appwnd, const bool isApp)
{
boost::shared_ptr<RBX::Game> game(Roblox::getpreloadedGame(isApp));
return new RobloxView(wnd, appwnd, game);
}
void RobloxView::executeJoinScript(const std::string& urlScript, const bool isApp, const bool isFromProtocolHandler)
{
boost::thread(boost::bind(&executeScript, game, urlScript, isApp, isFromProtocolHandler));
}
void RobloxView::setUIMessage(const std::string& message)
{
if (shared_ptr<DataModel> dm = game->getDataModel())
dm->submitTask(boost::bind(&RobloxView::setUIMessage_, this, message), DataModelJob::Write);
}
void RobloxView::setUIMessage_(const std::string& message)
{
if (shared_ptr<DataModel> dm = game->getDataModel())
{
if (message.length() > 0)
{
dm->setUiMessage(message);
}
else
{
dm->clearUiMessage();
}
if (GuiService*gs = dm->create<GuiService>())
gs->setUiMessage(GuiService::UIMESSAGE_INFO, message);
}
}
+141
View File
@@ -0,0 +1,141 @@
#pragma once
#include "boost/shared_ptr.hpp"
#include "boost/scoped_ptr.hpp"
#include "boost/thread.hpp"
#include "v8datamodel/game.h"
#include "Util/KeyCode.h"
#include "G3D/Vector2.h"
#include "rbx/signal.h"
namespace RBX
{
class DataModel;
class ViewBase;
class FunctionMarshaller;
namespace Tasks
{
class Sequence;
}
namespace Reflection
{
class PropertyDescriptor;
}
}
class UserInput;
class RobloxView
{
boost::scoped_ptr<RBX::ViewBase> view;
boost::shared_ptr<RBX::Game> game;
boost::scoped_ptr<UserInput> userInput;
boost::scoped_ptr<class LeaveGameVerb> leaveGameVerb;
boost::scoped_ptr<RBX::Verb> fullscreenVerb;
boost::scoped_ptr<RBX::Verb> studioVerb;
boost::scoped_ptr<RBX::Verb> screenshotVerb;
boost::shared_ptr<class ShutdownClientVerb> shutdownClientVerb;
RBX::FunctionMarshaller* marshaller;
rbx::signals::scoped_connection placeIDChangeConnection;
void *viewWindow;
void *appWindow;
boost::shared_ptr<RBX::Tasks::Sequence> sequence;
class RenderJob;
class UserInputJob;
boost::shared_ptr<RenderJob> renderJob;
boost::shared_ptr<UserInputJob> userInputJob;
static boost::shared_ptr<RobloxView> rbxView;
static boost::shared_ptr<RBX::Game> startGame(std::string urlScript, const bool isApp);
void doTeleport(std::string url, std::string ticket, std::string script);
void onPlaceIDChanged(const RBX::Reflection::PropertyDescriptor* desc);
void doUnbindWorkspace();
void initializeInput();
public:
// N.B.: These are clones of other Roblox defines, need to be here to get around Objective-C vs. Cpp issues.
// The values aren't literally duplicated, they are transduced by switch statements. So this is safe-- but annoying. --TP
enum EventType { NO_EVENT,
MOUSE_RIGHT_BUTTON_DOWN,
MOUSE_RIGHT_BUTTON_UP,
MOUSE_LEFT_BUTTON_DOWN,
MOUSE_LEFT_BUTTON_UP,
MOUSE_MOVE,
MOUSE_DELTA,
MOUSE_IDLE, // during runtime, this event is guaranteed every step if no other mouse event
MOUSE_WHEEL_FORWARD,
MOUSE_WHEEL_BACKWARD,
KEY_DOWN,
KEY_UP,
KILLFOCUS,
SETFOCUS,
// these can come later - for now, when moving out of a window,
// just make sure a mouse up comes along if the mouse was previously down...??
// MOUSE_DOWN_DOUBLE_CLICK,
// MOUSE_MOVE_OUT_WINDOW
MOUSE_MIDDLE_BUTTON_DOWN,
MOUSE_MIDDLE_BUTTON_UP
};
RobloxView(void *viewwnd, void *appwnd, boost::shared_ptr<RBX::Game> game);
~RobloxView(void);
void handleUserMessage(EventType event, unsigned int wParam, unsigned int lParam);
void handleFocus(bool focus);
void handleMouseInside(bool inside);
void handleMouse(EventType event, int x, int y, unsigned int modifiers);
void handleKey(EventType event, RBX::KeyCode keyCode, RBX::ModCode modifiers);
void handleScrollWheel(float delta, int x, int y);
void processInput();
// request a shutdown from the game
Boolean requestShutdownClient();
// Some Cocoa/windowing help (methods defined in RobloxView.mm)
void getCursorPos(G3D::Vector2 *pPos);
void setCursorPos(G3D::Vector2 pPos);
void marshalTeleport(std::string url, std::string ticket, std::string script);
// Game verbs
void leaveGame();
void handleLeaveGame();
void shutdownClient();
void handleShutdownClient();
void toggleFullScreen();
void handleToggleFullScreen();
bool isFullscreenMode();
void doShutdownDataModel();
void stopJobs();
unsigned int width;
unsigned int height;
void setBounds(unsigned int width, unsigned int height);
static RobloxView *start_game(void *wnd, void *appwnd, std::string urlScript, const bool isApp);
static RobloxView *init_game(void *wnd, void* appwnd, const bool isApp);
void executeJoinScript(const std::string& urlScript, const bool isApp, const bool isFromProtocolHandler);
void setUIMessage(const std::string& message);
boost::shared_ptr<RBX::DataModel> getDataModel() { return game ? game->getDataModel() : boost::shared_ptr<RBX::DataModel>(); }
private:
void bindToWorkspace();
void defineConcurrencyRules();
void setUIMessage_(const std::string& message);
};
+69
View File
@@ -0,0 +1,69 @@
//
// RobloxView.mm
// MacClient
//
// Created by Tony on 2/9/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <ApplicationServices/ApplicationServices.h>
#import "RobloxOgreView.h"
#include "RobloxView.h"
#include "FunctionMarshaller.h"
#include "RobloxPlayerAppDelegate.h"
inline void flipY(NSView *view, NSPoint &pt)
{
BOOL flipped = [view isFlipped];
if (!flipped)
{
NSRect bounds = [view bounds];
CGFloat height = bounds.size.height;
pt.y = height - pt.y;
}
}
void RobloxView::getCursorPos(G3D::Vector2 *pPos)
{
if(RobloxOgreView* ogreView = (RobloxOgreView*) viewWindow)
{
CGPoint virtualCursorPos = [ogreView getVirtualCursorPos];
pPos->x = virtualCursorPos.x;
pPos->y = virtualCursorPos.y;
}
}
void RobloxView::setCursorPos(G3D::Vector2 pPos)
{
if(RobloxOgreView* ogreView = (RobloxOgreView*) viewWindow)
[ogreView setVirtualCursorPos:CGPointMake(G3D::iRound(pPos.x), G3D::iRound(pPos.y))];
}
void RobloxView::marshalTeleport(std::string url, std::string ticket, std::string script)
{
marshaller->Submit(boost::bind(&RobloxView::doTeleport, this, url, ticket, script));
}
void RobloxView::doTeleport(std::string url, std::string ticket, std::string script)
{
RobloxPlayerAppDelegate *appDelegate = (RobloxPlayerAppDelegate*)appWindow;
NSString *ticketString = [NSString stringWithCString:ticket.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *urlString = [NSString stringWithCString:url.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *scriptString = [NSString stringWithCString:script.c_str() encoding:[NSString defaultCStringEncoding]];
[appDelegate teleport:ticketString withAuthentication:urlString withScript:scriptString];
}
bool RobloxView::isFullscreenMode()
{
if(RobloxOgreView* ogreView = (RobloxOgreView*) viewWindow)
return ogreView.fullScreen;
return false;
}
+9
View File
@@ -0,0 +1,9 @@
/*
* StdAfx.h
* MacClient
*
* Created by David York on 7/3/12.
* Copyright 2012 ROBLOX. All rights reserved.
*
*/
+498
View File
@@ -0,0 +1,498 @@
#include "Util/Rect.h"
#include "Util/NavKeys.h"
#include "Util/Standardout.h"
#include "Util/NavKeys.h"
#include "v8datamodel/ContentProvider.h"
#include "V8DataModel/Workspace.h"
#include "V8DataModel/GameSettings.h"
#include "V8DataModel/UserInputService.h"
#include "RbxG3D/RbxTime.h"
#include "G3D/System.h"
#include "UserInput.h"
#include "V8DataModel/GameSettings.h"
#include "V8DataModel/GameBasicSettings.h"
#include "RobloxView.h"
#include "V8DataModel/SleepingJob.h"
#include "V8DataModel/DataModelJob.h"
#define DXINPUT_TRACE __noop
DYNAMIC_FASTFLAGVARIABLE(MouseDeltaWhenNotMouseLocked, false)
FASTFLAG(UserAllCamerasInLua)
namespace RBX
{
class DataModel;
}
class UserInputJob : public RBX::SleepingJob
{
private:
UserInput* const userInput;
void processQueue()
{
Event event;
while(queue.pop_if_present(event))
{
userInput->ProcessUserInputMessage(event.eventType, event.eventState, event.wParam, event.lParam);
}
}
public:
struct Event
{
RBX::InputObject::UserInputType eventType;
RBX::InputObject::UserInputState eventState;
unsigned int wParam;
unsigned int lParam;
};
rbx::safe_queue<Event> queue;
UserInputJob(UserInput* userInput, shared_ptr<RBX::DataModelArbiter> arbiter)
:RBX::SleepingJob("UserInput", RBX::DataModelJob::Write, false, arbiter, RBX::Time::Interval(0.01), 60)
,userInput(userInput)
{}
virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
{
RBX::DataModel::scoped_write_request request( userInput->game->getDataModel().get() );
processQueue();
sleep();
return RBX::TaskScheduler::Stepped;
}
};
UserInput::UserInput(RobloxView *wnd, shared_ptr<RBX::Game> newGame)
: externallyForcedKeyDown(0),
isMouseCaptured(false),
isMouseInside(false),
wnd(wnd),
leftMouseButtonDown(false),
rightMouseDown(false),
autoMouseMove(false),
posToWrapTo(0,0),
wrapping(false)
{
setGame(newGame);
}
void UserInput::setGame(shared_ptr<RBX::Game> newGame)
{
game = newGame;
if(shared_ptr<RBX::DataModel> dataModel = game->getDataModel())
{
renderStepConnection.disconnect();
job.reset(new UserInputJob(this, dataModel));
RBX::TaskScheduler::singleton().add(job);
sdlGameController = shared_ptr<SDLGameController>(new SDLGameController(dataModel));
if (RBX::RunService* runService = RBX::ServiceProvider::create<RBX::RunService>(dataModel.get()))
{
renderStepConnection = runService->earlyRenderSignal.connect(boost::bind(&UserInput::processInput, this));
}
}
}
UserInput::~UserInput()
{
sdlGameController.reset();
RBX::TaskScheduler::singleton().removeBlocking(job);
}
void UserInput::PostUserInputMessage(RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, unsigned int wParam, unsigned int lParam)
{
BufferedEvent bufferedEvent;
bufferedEvent.userInputType = eventType;
bufferedEvent.userInputState = eventState;
bufferedEvent.wParam = wParam;
bufferedEvent.lParam = lParam;
boost::mutex::scoped_lock lock(bufferMutex);
bufferedEvents.push_back(bufferedEvent);
}
void UserInput::processInput()
{
std::vector<BufferedEvent> tempVec;
{
boost::mutex::scoped_lock lock(bufferMutex);
bufferedEvents.swap(tempVec);
}
for (std::vector<BufferedEvent>::iterator iter = tempVec.begin(); iter != tempVec.end(); ++iter)
{
ProcessUserInputMessage(iter->userInputType, iter->userInputState, iter->wParam, iter->lParam);
}
}
extern void MacWriteFastLogDump();
void UserInput::ProcessUserInputMessage(RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, unsigned int wParam, unsigned int lParam)
{
RobloxCritSecLoc lock(diSection, "ProcessUserInputMessage");
bool cursorMoved = false;
bool leftMouseUp = false;
RBX::Vector2 wrapMouseDelta;
G3D::Vector2 mouseDelta;
switch (eventType)
{
case RBX::InputObject::TYPE_KEYBOARD:
{
if (wParam == RBX::SDLK_F8 && lParam == RBX::KMOD_NONE)
MacWriteFastLogDump();
const RBX::ModCode modCode = (RBX::ModCode) lParam;
const bool keyDown = (eventState == RBX::InputObject::INPUT_STATE_BEGIN);
const char key = RBX::UserInputService::getModifiedKey((RBX::KeyCode) wParam,(RBX::ModCode) lParam);
if(shared_ptr<RBX::DataModel> dataModel = game->getDataModel())
if(RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(dataModel.get()))
userInputService->setKeyState((RBX::KeyCode) wParam, modCode, key, keyDown);
RBX::KeyCode keyCode = (RBX::KeyCode) wParam;
if(eventState == RBX::InputObject::INPUT_STATE_END && keyboardInputObjects.find(keyCode) != keyboardInputObjects.end())
{
shared_ptr<RBX::InputObject> keyInput = keyboardInputObjects[keyCode];
keyInput->setInputState(RBX::InputObject::INPUT_STATE_END);
keyInput->mod = (RBX::ModCode) lParam;
keyInput->modifiedKey = key;
sendEvent(keyInput);
keyboardInputObjects.erase(keyCode);
}
else
{
shared_ptr<RBX::InputObject> keyInput = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(eventType, eventState, keyCode, (RBX::ModCode) lParam, key, game->getDataModel().get());
keyboardInputObjects[keyCode] = keyInput;
sendEvent(keyInput);
}
break;
}
case RBX::InputObject::TYPE_MOUSEBUTTON1:
case RBX::InputObject::TYPE_MOUSEBUTTON2:
case RBX::InputObject::TYPE_MOUSEBUTTON3: // Intentional fall thru (todo: lets clean this up)
{
RBX::InputObject event(eventType, eventState, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0), game->getDataModel().get());
RBXASSERT(event.isLeftMouseDownEvent() || event.isLeftMouseUpEvent() || event.isRightMouseDownEvent() || event.isRightMouseUpEvent() || event.isMiddleMouseDownEvent() || event.isMiddleMouseUpEvent());
G3D::Vector2 pos((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
sendMouseEvent(eventType, eventState, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
if ( event.isLeftMouseDownEvent() )
{
leftMouseButtonDown = true;
leftMouseUp = false;
isMouseCaptured = true;
}
if ( event.isLeftMouseUpEvent() )
{
leftMouseButtonDown = false;
leftMouseUp = true;
isMouseCaptured = false;
}
if ( event.isRightMouseDownEvent() )
rightMouseDown = true;
if ( event.isRightMouseUpEvent() )
rightMouseDown = false;
break;
}
case RBX::InputObject::TYPE_MOUSEMOVEMENT:
{
mouseDelta = G3D::Vector2((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
autoMouseMove = false;
cursorMoved = true;
accumulatedFractionalMouseDelta += mouseDelta * RBX::GameBasicSettings::singleton().getMouseSensitivity();
mouseDelta.x = (int) accumulatedFractionalMouseDelta.x;
mouseDelta.y = (int) accumulatedFractionalMouseDelta.y;
accumulatedFractionalMouseDelta -= mouseDelta;
doWrapMouse(mouseDelta, wrapMouseDelta);
break;
}
case RBX::InputObject::TYPE_MOUSEWHEEL:
{
int zDelta = (int) wParam; // wheel rotation
G3D::Vector2 pos((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
sendMouseEvent(RBX::InputObject::TYPE_MOUSEWHEEL,
RBX::InputObject::INPUT_STATE_CHANGE,
RBX::Vector3(pos.x,pos.y,zDelta),
RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
break;
}
case RBX::InputObject::TYPE_FOCUS:
{
if(eventState == RBX::InputObject::INPUT_STATE_BEGIN)
DXINPUT_TRACE(std::string("UserInput::WM_SETFOCUS\n").c_str());
else
DXINPUT_TRACE(std::string("UserInput::WM_KILLFOCUS\n").c_str());
shared_ptr<RBX::InputObject> event = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(RBX::InputObject::TYPE_FOCUS,eventState,game->getDataModel().get());
sendEvent(event);
break;
}
default:
{
break;
}
}
/////////// DONE PROCESSING ////////////////
shared_ptr<RBX::DataModel> dataModel = game->getDataModel();
if (wrapMouseDelta != RBX::Vector2::zero())
{
if (RBX::Workspace* workspace = dataModel->getWorkspace())
{
if (!FFlag::UserAllCamerasInLua || !workspace->getCamera()->hasClientPlayer())
{
if ( workspace->getCamera()->getCameraType() != RBX::Camera::CUSTOM_CAMERA )
{
workspace->onWrapMouse(wrapMouseDelta);
}
}
}
sendMouseEvent(RBX::InputObject::TYPE_MOUSEDELTA, RBX::InputObject::INPUT_STATE_CHANGE, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
sendMouseEvent(RBX::InputObject::TYPE_MOUSEMOVEMENT, RBX::InputObject::INPUT_STATE_CHANGE, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
}
else if (cursorMoved)
{
if (DFFlag::MouseDeltaWhenNotMouseLocked)
{
sendMouseEvent(RBX::InputObject::TYPE_MOUSEMOVEMENT, RBX::InputObject::INPUT_STATE_CHANGE, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(mouseDelta.x, mouseDelta.y, 0));
}
else
{
sendMouseEvent(RBX::InputObject::TYPE_MOUSEMOVEMENT, RBX::InputObject::INPUT_STATE_CHANGE, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
}
}
}
void UserInput::sendMouseEvent(RBX::InputObject::UserInputType mouseEventType, RBX::InputObject::UserInputState mouseEventState, const RBX::Vector3& position, const RBX::Vector3& delta)
{
shared_ptr<RBX::InputObject> mouseEventObject;
if( inputObjectMap.find(mouseEventType) == inputObjectMap.end() )
{
mouseEventObject = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(mouseEventType, mouseEventState, position, delta, game->getDataModel().get());
inputObjectMap[mouseEventType] = mouseEventObject;
}
else
{
mouseEventObject = inputObjectMap[mouseEventType];
mouseEventObject->setInputState(mouseEventState);
mouseEventObject->setPosition(position);
mouseEventObject->setDelta(delta);
inputObjectMap[mouseEventType] = mouseEventObject;
}
sendEvent(mouseEventObject);
}
void UserInput::sendEvent(const shared_ptr<RBX::InputObject>& event)
{
if (shared_ptr<RBX::DataModel> dataModel = game->getDataModel())
{
if ( RBX::UserInputService* inputService = RBX::ServiceProvider::find<RBX::UserInputService>(dataModel.get()) )
{
inputService->fireInputEvent(event, NULL);
}
}
}
bool UserInput::isFullScreenMode() const
{
return wnd->isFullscreenMode();
}
G3D::Rect2D UserInput::getWindowRect() const // { return windowRect; }
{
G3D::Rect2D answer(G3D::Vector2(wnd->width, wnd->height));
return answer;
}
G3D::Vector2int16 UserInput::getWindowSize() const
{
G3D::Rect2D windowRect = getWindowRect();
return G3D::Vector2int16((G3D::int16) windowRect.width(), (G3D::int16) windowRect.height());
}
RBX::TextureProxyBaseRef UserInput::getGameCursor(RBX::Adorn* adorn)
{
return UserInputBase::getGameCursor(adorn);
}
void UserInput::doWrapHybrid(bool cursorMoved, bool leftMouseUp, G3D::Vector2& wrapMouseDelta, G3D::Vector2& wrapMousePosition, G3D::Vector2& posToWrapTo)
{
}
bool UserInput::movementKeysDown()
{
return ( keyDownInternal(RBX::SDLK_w) || keyDownInternal(RBX::SDLK_s) || keyDownInternal(RBX::SDLK_a) || keyDownInternal(RBX::SDLK_d)
|| keyDownInternal(RBX::SDLK_UP)|| keyDownInternal(RBX::SDLK_DOWN) || keyDownInternal(RBX::SDLK_LEFT) || keyDownInternal(RBX::SDLK_RIGHT) );
}
// will be called repeatedly. ignore repeated calls.
void UserInput::onMouseInside()
{
G3D::Vector2 pos;
wnd->getCursorPos(&pos);
wrapMousePosition = pos - getWindowRect().center(); // i.e. - pull towards the origin - remove chatter
if(isMouseInside)
return; // we know. ignore.
RobloxCritSecLoc lock(diSection, "onMouseEnter");
isMouseInside = true;
};
void UserInput::onMouseLeave()
{
if (!isMouseInside)
return;
RobloxCritSecLoc lock(diSection, "onMouseLeave");
isMouseInside = false;
};
void UserInput::centerCursor()
{
wrapMousePosition = RBX::Vector2::zero();
wnd->setCursorPos(getWindowRect().center());
}
G3D::Vector2 UserInput::getCursorPosition()
{
RobloxCritSecLoc lock(diSection, "getCursorPosition");
G3D::Vector2 pt;
wnd->getCursorPos(&pt);
return pt;
}
bool UserInput::keyDownInternal(RBX::KeyCode code) const
{
if (externallyForcedKeyDown && (code == externallyForcedKeyDown))
{
return true;
}
boost::unordered_map<RBX::KeyCode,shared_ptr<RBX::InputObject> >::const_iterator iter = keyboardInputObjects.find(code);
if ( iter != keyboardInputObjects.end() )
{
if ( iter->second && iter->second.get() )
{
return (iter->second->getUserInputState() != RBX::InputObject::INPUT_STATE_END);
}
}
return false;
}
bool UserInput::keyDown(RBX::KeyCode code) const
{
return keyDownInternal(code);
}
void UserInput::setKeyState(RBX::KeyCode code, RBX::ModCode modCode, char modifiedKey, bool isDown)
{
RobloxCritSecLoc lock(diSection, "setKeyState");
externallyForcedKeyDown = isDown ? code : 0;
}
bool preventWrapMouse()
{
// unlikely to get here
return true;
}
void UserInput::doWrapMouse(const G3D::Vector2& delta, G3D::Vector2& wrapMouseDelta)
{
RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(game->getDataModel().get());
if (!userInputService)
{
return;
}
switch (userInputService->getMouseWrapMode())
{
case RBX::UserInputService::WRAP_NONEANDCENTER:
{
centerCursor();
}
case RBX::UserInputService::WRAP_NONE: // intentional fall thru
case RBX::UserInputService::WRAP_CENTER:
{
UserInputUtil::wrapMouseCenter(delta,
wrapMouseDelta,
this->wrapMousePosition);
break;
}
case RBX::UserInputService::WRAP_HYBRID:
{
UserInputUtil::wrapMousePos(delta,
wrapMouseDelta,
wrapMousePosition,
getWindowSize(),
posToWrapTo,
autoMouseMove);
break;
}
case RBX::UserInputService::WRAP_AUTO:
{
// 1. If movement keys are down - keep the mouse in the window
// 2. If the left mouse button is down (we are dragging) - keep in the window
if (movementKeysDown() || isMouseCaptured)
{
UserInputUtil::wrapMouseBorderLock(delta,
wrapMouseDelta,
this->wrapMousePosition,
getWindowSize());
}
// 4. OK - we're in PlayMode (i.e. character)
else if (isFullScreenMode())
{
UserInputUtil::wrapFullScreen(delta,
wrapMouseDelta,
this->wrapMousePosition,
getWindowSize());
}
// We no longer want mouse wrap and camera auto-pan at the horizontal window extents.
else
{
UserInputUtil::wrapMouseNone(delta,
wrapMouseDelta,
this->wrapMousePosition);
}
break;
}
}
wnd->setCursorPos(getWindowRect().center() + wrapMousePosition);
}
+150
View File
@@ -0,0 +1,150 @@
#pragma once
#include "UserInputUtil.h"
#include "Util/UserInputBase.h"
#include "Util/RunStateOwner.h"
#include "Util/Rect.h"
#include "SDLGameController.h"
#include "GfxBase/TextureProxyBase.h"
namespace RBX {
class Game;
}
class RobloxCriticalSection
{
private:
public:
int callers;
std::string recentCaller;
std::string olderCaller;
RobloxCriticalSection()
: callers(0)
{}
};
class RobloxCritSecLoc
{
private:
RobloxCriticalSection& robloxCriticalSection;
public:
RobloxCritSecLoc( RobloxCriticalSection& cs, const std::string& location)
: robloxCriticalSection(cs)
{
}
~RobloxCritSecLoc()
{
}
};
#define NKEYSTATES 512
struct BufferedEvent
{
RBX::InputObject::UserInputType userInputType;
RBX::InputObject::UserInputState userInputState;
unsigned int wParam;
unsigned int lParam;
};
class UserInputJob;
class UserInput
: public RBX::UserInputBase
{
private:
shared_ptr<UserInputJob> job;
mutable RobloxCriticalSection diSection;
rbx::signals::scoped_connection renderStepConnection;
// Mouse Stuff
bool isMouseCaptured; // poor man's tracker of button state
bool leftMouseButtonDown;
G3D::Vector2 wrapMousePosition; // in normalized coordinates (center is 0,0. radius is getWrapRadius)
bool wrapping;
bool rightMouseDown;
bool autoMouseMove;
// Keyboard Stuff
int externallyForcedKeyDown; // + this from exteranal source (like a gui button)
class RobloxView * wnd;
boost::mutex bufferMutex;
std::vector<BufferedEvent> bufferedEvents;
// InputObject stuff
boost::unordered_map<RBX::InputObject::UserInputType,shared_ptr<RBX::InputObject> > inputObjectMap;
boost::unordered_map<RBX::KeyCode,shared_ptr<RBX::InputObject> > keyboardInputObjects;
////////////////////////////////
//
// Events
void sendEvent(const shared_ptr<RBX::InputObject>& event);
void sendMouseEvent(RBX::InputObject::UserInputType mouseEventType, RBX::InputObject::UserInputState mouseEventState, const RBX::Vector3& position, const RBX::Vector3& delta);
////////////////////////////////////
//
// Keyboard Mouse
bool isMouseInside;
G3D::Vector2 posToWrapTo;
G3D::Vector2 accumulatedFractionalMouseDelta;
////////////////////////////////////
// Gamepad Stuff
shared_ptr<SDLGameController> sdlGameController;
// window stuff
RBX::Vector2int16 getWindowSize() const;
G3D::Rect2D getWindowRect() const;
bool isFullScreenMode() const;
bool movementKeysDown();
bool keyDownInternal(RBX::KeyCode code) const;
void doWrapMouse(const G3D::Vector2& delta, G3D::Vector2& wrapMouseDelta);
G3D::Vector2 getGameCursorPositionInternal();
G3D::Vector2 getGameCursorPositionExpandedInternal(); // prevent hysteresis
G3D::Vector2 getWindowsCursorPositionInternal();
RBX::Vector2 getCursorPositionInternal();
void processInput();
public:
shared_ptr<RBX::Game> game;
////////////////////////////////
//
// UserInputBase
/*implement*/ RBX::Vector2 getCursorPosition();
void doWrapHybrid(bool cursorMoved, bool leftMouseUp, G3D::Vector2& wrapMouseDelta, G3D::Vector2& wrapMousePosition, G3D::Vector2& posToWrapTo);
/*implement*/ bool keyDown(RBX::KeyCode code) const;
/*implement*/ void setKeyState(RBX::KeyCode code, RBX::ModCode modCode, char modifiedKey, bool isDown);
/*implement*/ void centerCursor();
/*override*/ RBX::TextureProxyBaseRef getGameCursor(RBX::Adorn* adorn);
void PostUserInputMessage(RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, unsigned int wParam, unsigned int lParam);
// Call this only within a DataModel lock:
void ProcessUserInputMessage(RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, unsigned int wParam, unsigned int lParam);
UserInput(RobloxView *wnd, shared_ptr<RBX::Game> newGame);
~UserInput();
void setGame(shared_ptr<RBX::Game> newGame);
void onMouseInside();
void onMouseLeave();
};
+234
View File
@@ -0,0 +1,234 @@
#include "UserInputUtil.h"
#include "Util/Rect.h"
#include "util/standardout.h"
#include "V8DataModel/GameSettings.h"
using RBX::Vector2;
const float UserInputUtil::HybridSensitivity = 8.0f;
const float UserInputUtil::MouseTug = 15.0f;
// horizontal - will keep doing deltas
// vertial - will peg inset 2 pixels
//
void UserInputUtil::wrapFullScreen(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition,
const Vector2& windowSize)
{
float wrapPositionY = wrapMousePosition.y + delta.y;
wrapMouseBorderLock(delta, wrapMouseDelta, wrapMousePosition, windowSize);
wrapMouseDelta.y = 0.0;
wrapMousePosition.y = wrapPositionY;
float halfHeight = (windowSize.y * 0.5);
float wrapPositionX = wrapMousePosition.x;
float halfWidth = (windowSize.x * 0.5);
wrapMousePosition.x = G3D::clamp(wrapPositionX,-halfWidth,halfWidth);
wrapMousePosition.y = G3D::clamp(wrapPositionY,-halfHeight,halfHeight);
// Setting this to zero prevents the camera from panning automatically near the extents of the screen.
wrapMouseDelta = Vector2::zero();
}
void UserInputUtil::wrapMouseHorizontalTransition(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition,
const Vector2& windowSize)
{
float wrapPositionY = wrapMousePosition.y + delta.y;
wrapMouseBorderTransition(delta, wrapMouseDelta, wrapMousePosition, windowSize);
wrapMouseDelta.y = 0.0;
wrapMousePosition.y = wrapPositionY;
}
void UserInputUtil::wrapMouseBorder(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition,
const Vector2& windowSize,
const int borderWidth,
const float creepFactor)
{
Vector2 halfSize = windowSize * 0.5;
RBX::Rect inner = RBX::Rect(-halfSize, halfSize).inset(borderWidth); // in Wrap Coordinates
Vector2 oldPosition = wrapMousePosition;
inner.unionWith(oldPosition); // now union of the border and old position - ratchet
Vector2 newPositionUnclamped = oldPosition + delta;
Vector2 newPositionClamped = inner.clamp(newPositionUnclamped);
Vector2 positiveDistanceInBorder = newPositionUnclamped - newPositionClamped;
wrapMousePosition = newPositionClamped + (positiveDistanceInBorder * creepFactor);
wrapMouseDelta += positiveDistanceInBorder;
}
void UserInputUtil::wrapMouseBorderLock(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition,
const Vector2& windowSize)
{
wrapMouseBorder(
delta,
wrapMouseDelta,
wrapMousePosition,
windowSize,
6,
0.0f);
}
void UserInputUtil::wrapMouseBorderTransition(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition,
const Vector2& windowSize)
{
wrapMouseBorder(
delta,
wrapMouseDelta,
wrapMousePosition,
windowSize,
20,
0.05f);
}
void UserInputUtil::wrapMouseNone(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition)
{
wrapMouseDelta = Vector2::zero();
wrapMousePosition += delta;
}
void UserInputUtil::wrapMouseCenter(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition)
{
wrapMouseDelta += delta;
// don't move the cursor....
// wrapMousePosition = G3D::Vector2::zero();
}
void UserInputUtil::wrapMouseHorizontalCenter(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition)
{
wrapMouseDelta.x += delta.x;
// wrapMousePosition = G3D::Vector2::zero();
}
void UserInputUtil::wrapMousePos(const Vector2& delta,
Vector2& wrapMouseDelta,
Vector2& wrapMousePosition,
const Vector2& windowSize,
Vector2& posToWrapTo,
bool autoMoveMouse)
{
if(posToWrapTo.length() > 2)
{
Vector2 windowDelta = posToWrapTo/windowSize;
wrapMouseDelta = windowDelta * HybridSensitivity;
posToWrapTo -= (wrapMouseDelta * HybridSensitivity * 0.266f); // 0.266 is a tuning constant
float xDiff = std::abs(wrapMousePosition.x/wrapMousePosition.length()) * 0.3f;
float yDiff = std::abs(wrapMousePosition.y/wrapMousePosition.length()) * 0.4f;
if(autoMoveMouse && wrapMousePosition != Vector2::zero())
{
if(wrapMousePosition.x < 0)
{
wrapMousePosition.x += xDiff * wrapMouseDelta.length() * MouseTug;
if(wrapMousePosition.x > 0)
wrapMousePosition.x = 0;
}
else if (wrapMousePosition.x > 0)
{
wrapMousePosition.x -= xDiff * wrapMouseDelta.length()* MouseTug;
if(wrapMousePosition.x < 0)
wrapMousePosition.x = 0;
}
if(wrapMousePosition.y < 0)
{
wrapMousePosition.y += yDiff * wrapMouseDelta.length() * MouseTug;
if(wrapMousePosition.y > 0)
wrapMousePosition.y = 0;
}
else if (wrapMousePosition.y > 0)
{
wrapMousePosition.y -= yDiff * wrapMouseDelta.length() * MouseTug;
if(wrapMousePosition.y < 0)
wrapMousePosition.y = 0;
}
autoMoveMouse = false;
}
}
wrapMousePosition += delta;
}
// Maps DIK_* to RBX::SDLK_*
RBX::KeyCode UserInputUtil::directInputToKeyCode(DWORD diKey)
{
RBXASSERT(diKey>=0);
RBXASSERT(diKey<256);
static RBX::KeyCode keymap[256];
static bool initialized = false;
if (!initialized)
{
for ( int i=0; i<256; ++i )
keymap[i] = RBX::SDLK_UNKNOWN;
initialized = true;
}
return keymap[diKey];
}
// Maps RBX::RBX::SDLK_* to DIK_*
DWORD UserInputUtil::keyCodeToDirectInput(RBX::KeyCode keyCode)
{
static DWORD keymap[RBX::SDLK_LAST];
static bool initialized = false;
if (!initialized)
{
for ( int i=0; i<RBX::SDLK_LAST; ++i )
keymap[i] = 0;
initialized = true;
}
return keymap[keyCode];
}
// Maps RBX::RBX::SDLK_* to VK_*
DWORD UserInputUtil::keyCodeToVK(RBX::KeyCode keyCode)
{
static DWORD keymap[RBX::SDLK_LAST];
static bool initialized = false;
if (!initialized)
{
for ( int i=0; i<RBX::SDLK_LAST; ++i )
keymap[i] = 0;
initialized = true;
}
return keymap[keyCode];
}
RBX::ModCode UserInputUtil::createModCode(const DiKeys& diKeys)
{
unsigned int modCode = 0;
return (RBX::ModCode)modCode;
}
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include "Util/KeyCode.h"
#include "V8DataModel/InputObject.h"
#include "rbx/debug.h"
#include "G3D/Vector2.h"
#define DIRECTINPUT_VERSION 0x0800
#define WM_KEYUP 0
#define WM_KEYDOWN 1
#define WM_SETFOCUS 10
#define WM_KILLFOCUS 11
#define GET_X_LPARAM(l) ((short)(l & 0x0000FFFF))
#define GET_Y_LPARAM(l) ((short)((l & 0xFFFF0000) >> 16))
#define MAKEXYLPARAM(x, y) (((unsigned short) x) | ((unsigned short) y) << 16)
class UserInputUtil
{
private:
static void wrapMouseBorder(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition,
const G3D::Vector2& windowSize,
const int borderWidth,
const float creepFactor);
public:
typedef uint8_t DiKeys[256];
static const float HybridSensitivity;
static const float MouseTug;
static RBX::ModCode createModCode(const DiKeys& diKeys);
static RBX::InputObject::UserInputType msgToEventType(unsigned int uMsg);
static RBX::InputObject::UserInputState msgToEventState(unsigned int uMsg);
static DWORD keyCodeToDirectInput(RBX::KeyCode keyCode);
static RBX::KeyCode directInputToKeyCode(DWORD diKey);
static DWORD keyCodeToVK(RBX::KeyCode diKey);
// static G3D::Vector2 didodToVector2(const DIDEVICEOBJECTDATA& didod);
//
static void wrapMouseNone(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition);
static void wrapFullScreen(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition,
const G3D::Vector2& windowSize);
static void wrapMouseHorizontalTransition(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition,
const G3D::Vector2& windowSize);
static void wrapMouseBorderLock(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition,
const G3D::Vector2& windowSize);
static void wrapMouseBorderTransition(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition,
const G3D::Vector2& windowSize);
static void wrapMouseCenter(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition);
static void wrapMouseHorizontalCenter(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition);
static void wrapMousePos(const G3D::Vector2& delta,
G3D::Vector2& wrapMouseDelta,
G3D::Vector2& wrapMousePosition,
const G3D::Vector2& windowSize,
G3D::Vector2& posToWrapTo,
bool autoMoveMouse);
};
+61
View File
@@ -0,0 +1,61 @@
//
// main.m
// SimplePlayerMac
//
// Created by Tony on 10/26/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include "Roblox.h"
#include "v8datamodel/ContentProvider.h"
FASTFLAGVARIABLE(GraphicsReportingInitErrorsToGAEnabled,true)
int main(int argc, char *argv[])
{
// Get the Location where the Resources are installed.
CFBundleRef mainBundle = CFBundleGetMainBundle();
CFURLRef resourceBundle = CFBundleCopyResourcesDirectoryURL(mainBundle);
char resourcePath[PATH_MAX];
if(CFURLGetFileSystemRepresentation(resourceBundle, TRUE, (UInt8 *)resourcePath, PATH_MAX))
{
CFRelease(resourceBundle);
chdir(resourcePath);
Roblox::setArgs(resourcePath, argc > 1 ? argv[1] : "f");
}
// Set up pool to prevent leaks
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSUserDefaults* standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults)
{
// Forced removal of "Special Characters menu item
// Has to be done before the app is loaded with the current defaults
[standardUserDefaults setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"];
[standardUserDefaults synchronize];
}
[pool release];
// need to get client settings before creating window
NSString* baseUrl = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"RbxBaseUrl"];
const char* s = [baseUrl UTF8String];
::SetBaseURL(s);
std::string appSettings;
FetchClientSettingsData(CLIENT_APP_SETTINGS_STRING, CLIENT_SETTINGS_API_KEY, &appSettings);
LoadClientSettingsFromString(CLIENT_APP_SETTINGS_STRING, appSettings, &RBX::ClientAppSettings::singleton());
int mainstat = NSApplicationMain(argc, (const char **) argv);
return mainstat;
}