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
@@ -0,0 +1,11 @@
//
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#include "AndroidSettingsService.h"
DATA_MAP_IMPL_START(AndroidSettingsService)
//IMPL_DATA(iPadMinimumVersion, 1);// iPad 2
DATA_MAP_IMPL_END()
@@ -0,0 +1,24 @@
//
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#ifndef __RobloxMobile__AndroidSettingsService__
#define __RobloxMobile__AndroidSettingsService__
#include "v8datamodel/FastLogSettings.h"
#include "util/Statistics.h"
#include <iostream>
class AndroidSettingsService : public RBX::FastLogJSON
{
public:
START_DATA_MAP(AndroidSettingsService)
// DECLARE_DATA_INT(iPadMinimumVersion)
END_DATA_MAP();
};
#endif /* __RobloxMobile__AndroidSettingsService__ */
+156
View File
@@ -0,0 +1,156 @@
// 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 "../RobloxMac/Roblox.h"
#include "JNIMain.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 (RBX::base_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;
RBX::JNI::sendAppEvent(pClosure);
}
}
void FunctionMarshaller::Submit(boost::function<void()> job)
{
Closure *pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = NULL;
RBX::JNI::postAppEvent(pClosure);
}
void FunctionMarshaller::ProcessMessages()
{
RBX::JNI::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);
};
}
+107
View File
@@ -0,0 +1,107 @@
#include "JNIUtil.h"
#include <string>
#include <stdint.h>
#include <jni.h>
#include <android/native_window.h> // requires ndk r5 or newer
#include <android/native_window_jni.h> // requires ndk r5 or newer
#include <strings.h>
#include "FastLog.h"
#include "util/Http.h"
#include "RbxFormat.h"
LOGGROUP(Android)
using namespace RBX;
using namespace RBX::JNI;
// FileSystem.cpp:
namespace RBX
{
namespace JNI
{
extern std::string fileSystemCacheDir;
} // namespace JNI
} // namespace RBX
extern "C"
{
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityCurlTest_nativeOnStart(JNIEnv *jenv, jclass obj)
{
FLog::SetValue("Android", "10", FASTVARTYPE_STATIC, true);
FLog::SetValue("Network", "10", FASTVARTYPE_STATIC, true);
FLog::SetValue("HttpTrace", "10", FASTVARTYPE_DYNAMIC, true);
FLog::SetValue("HttpTraceStdout", "True", FASTVARTYPE_DYNAMIC, true);
// FLog::SetValue("HttpTextOnly", "False", FASTVARTYPE_STATIC, true);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityCurlTest_nativeOnResume(JNIEnv *jenv, jclass obj)
{
FASTLOG(FLog::Android, "nativeOnResume");
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityCurlTest_nativeOnPause(JNIEnv *jenv, jclass obj)
{
FASTLOG(FLog::Android, "nativeOnPause");
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityCurlTest_nativeOnStop(JNIEnv *jenv, jclass obj)
{
FASTLOG(FLog::Android, "nativeOnStop");
}
JNIEXPORT jstring JNICALL Java_wtf_watrbx_client_ActivityCurlTest_nativeGetURL(JNIEnv *jenv, jclass obj, jstring jsURL)
{
std::string url = jstringToStdString(jenv, jsURL);
Http http(url.c_str());
try
{
std::string response;
bool externalRequest = url.find("watrbx.wtf") == std::string::npos && url.find("robloxlabs.com") == std::string::npos;
http.get(response, externalRequest);
FASTLOGS(FLog::Android, "nativeGetURL: %s", response.c_str());
return jenv->NewStringUTF(response.c_str());
}
catch (const RBX::base_exception& e)
{
FASTLOGS(FLog::Android, "HTTP error: %s", e.what());
throw e;
}
}
JNIEXPORT jstring JNICALL Java_wtf_watrbx_client_ActivityCurlTest_nativePostAnalytics(JNIEnv *jenv, jclass obj, jstring jsCategory, jstring jsAction, jint value, jstring jsLabel)
{
std::string category = jstringToStdString(jenv, jsCategory);
std::string action = jstringToStdString(jenv, jsAction);
std::string label = jstringToStdString(jenv, jsLabel);
Http http("http://www.google-analytics.com/collect");
std::stringstream ss;
ss
<< "v=1"
<< "&tid=" << "UA-43420590-13"
<< "&cid=" << "1234"
<< "&t=" << "event"
<< "&ec=" << category
<< "&ea=" << action
<< "&ev=" << value
<< "&el=" << label;
try
{
std::string response;
http.post(ss, Http::kContentTypeDefaultUnspecified, true, response, true);
FASTLOGS(FLog::Android, "nativePostAnalytics: %s", response.c_str());
return jenv->NewStringUTF(response.c_str());
}
catch (const RBX::base_exception& e)
{
FASTLOGS(FLog::Android,"HTTP error: %s", e.what());
throw e;
}
}
} // extern "C"
+66
View File
@@ -0,0 +1,66 @@
#include "JNIUtil.h"
#include <string>
#include <stdint.h>
#include <jni.h>
#include <strings.h>
#include "FastLog.h"
#include "util/Http.h"
#include <android/log.h>
LOGGROUP(Android)
using namespace RBX;
using namespace RBX::JNI;
namespace RBX
{
namespace JNI
{
} // namespace JNI
} // namespace RBX
extern "C"
{
JNIEXPORT void JNICALL Java_wtf_watrbx_client_test_CurlTestHelper_initHttpJNI( JNIEnv* jenv, jclass obj) {
//__android_log_print(ANDROID_LOG_INFO, "InstrumentationTest", "JNIActivity initHttpJNI()");
Http::init(Http::WinHttp, Http::CookieSharingSingleProcessMultipleThreads);
}
JNIEXPORT jstring JNICALL Java_wtf_watrbx_client_test_CurlTestHelper_getCookieStringJNI( JNIEnv* jenv, jclass obj) {
std::string cookieStr;
Http::getCookiesForDomain("watrbx.wtf", cookieStr);
//__android_log_print(ANDROID_LOG_INFO, "InstrumentationTest", "JNIActivity getCookieStringJNI() %s", cookieStr.c_str());
return jenv->NewStringUTF(cookieStr.c_str());
}
JNIEXPORT jstring JNICALL Java_wtf_watrbx_client_test_CurlTestHelper_doCurlRequestJNI( JNIEnv* jenv, jclass obj, jstring jURL) {
std::string url = jstringToStdString(jenv, jURL);
//__android_log_print(ANDROID_LOG_INFO, "InstrumentationTest", "JNIActivity doCurlRequestJNI() %s", url.c_str());
Http http(url.c_str());
try
{
std::string response;
bool externalRequest = url.find("watrbx.wtf") == std::string::npos && url.find("robloxlabs.com") == std::string::npos;
http.get(response, externalRequest);
FASTLOGS(FLog::Android, "nativeGetURL: %s", response.c_str());
return jenv->NewStringUTF(response.c_str());
}
catch (const RBX::base_exception& e)
{
FASTLOGS(FLog::Android, "HTTP error: %s", e.what());
throw e;
}
}
} // extern "C"
+550
View File
@@ -0,0 +1,550 @@
#include "JNIGLActivity.h"
LOGVARIABLE(Android, 6)
FASTINTVARIABLE(AdColonyPercentage, 100)
FASTSTRINGVARIABLE(GoogleVideoAdUrl, "")
using namespace RBX;
using namespace RBX::JNI;
JavaVM* gJvm = NULL;
static ANativeWindow* aNativeWindow = NULL;
namespace RBX {
namespace SystemUtil { extern std::string mOSVersion; extern std::string mDeviceName; } // from Android/SystemUtil.cpp
namespace JNI {
int lastPlaceId;
void motionEventListening(std::string type) {
JNIEnv *env = NULL;
if (gJvm->AttachCurrentThread(&env, NULL) == JNI_OK) {
jclass callbackClass = JNI::getCallbackClass();
if (!callbackClass) {
return;
}
jmethodID motionEventListeningJMethod = JNI::getMotionEventListeningJMethod();
if (!motionEventListeningJMethod) {
return;
}
jstring typeString = env->NewStringUTF(type.c_str());
env->CallStaticVoidMethod(callbackClass, motionEventListeningJMethod, typeString);
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
return;
}
}
}
void textBoxFocused(shared_ptr<RBX::Instance> textbox) {
JNIEnv *env = NULL;
if (gJvm->AttachCurrentThread(&env, NULL) == JNI_OK) {
jclass callbackClass = getCallbackClass();
if (!callbackClass) {
return;
}
jmethodID showKeyboardJMethod = getShowKeyboardJMethod();
if (!showKeyboardJMethod) {
return;
}
jstring textBoxString = NULL;
RBX::TextBox* textBox = RBX::Instance::fastDynamicCast<RBX::TextBox>(
textbox.get());
if (textBox) {
textBoxString = env->NewStringUTF(
textBox->getBufferedText().c_str());
}
env->CallStaticVoidMethod(callbackClass, showKeyboardJMethod,
(jlong) (intptr_t) textBox, textBoxString);
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
return;
}
}
}
// Called by the engine's textBoxReleaseFocus event (connected in setupDatamodelCallbacks)
void textBoxFocusLost(shared_ptr<RBX::Instance> textBox) {
JNIEnv *env = NULL;
if (gJvm->AttachCurrentThread(&env, NULL) == JNI_OK) {
jclass callbackClass = getCallbackClass();
// Get the pointer to the hideKeyboard method in ActivityGlView
jmethodID hideKeyboardJMethod = getHideKeyboardJMethod();
if (!hideKeyboardJMethod)
{
RBX::StandardOut::singleton()->printf(MESSAGE_INFO, "JNI ERROR: Could not find hideKeyboard method.");
return;
}
env->CallStaticVoidMethod(callbackClass, hideKeyboardJMethod);
if (env->ExceptionOccurred())
{
env->ExceptionDescribe();
return;
}
}
}
}
}
static void doNativePurchaseRequest(shared_ptr<RBX::DataModel> dm,
shared_ptr<RBX::Instance> player, std::string productId) {
RBX::MarketplaceService* marketService = RBX::ServiceProvider::find<
RBX::MarketplaceService>(dm.get());
if (!marketService)
return;
JNIEnv *env = NULL;
if (gJvm->AttachCurrentThread(&env, NULL) == JNI_OK) {
jclass callbackClass = JNI::getCallbackClass();
if (!callbackClass) {
marketService->signalPromptNativePurchaseFinished(player,
productId, false);
return;
}
jmethodID promptNativePurchaseMethod =
JNI::getPromptNativePurchaseJMethod();
if (!promptNativePurchaseMethod) {
marketService->signalPromptNativePurchaseFinished(player,
productId, false);
return;
}
jstring productIdString = env->NewStringUTF(productId.c_str());
jstring userNameString = env->NewStringUTF(player->getName().c_str());
RBX::Network::Player* rawPlayer = RBX::Instance::fastDynamicCast<
RBX::Network::Player>(player.get());
env->CallStaticVoidMethod(callbackClass, promptNativePurchaseMethod,
(jlong) (intptr_t) rawPlayer, productIdString, userNameString);
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
marketService->signalPromptNativePurchaseFinished(player,
productId, false);
return;
}
} else {
marketService->signalPromptNativePurchaseFinished(player, productId, false);
}
}
static void nativePartyPurchaseRequested(weak_ptr<RBX::DataModel> weakDm,
shared_ptr<RBX::Instance> player, std::string productId) {
shared_ptr<RBX::DataModel> dm = weakDm.lock();
if (!dm)
return;
if (!dm.get())
return;
RBX::MarketplaceService* marketService = RBX::ServiceProvider::find<
RBX::MarketplaceService>(dm.get());
if (!marketService)
return;
if (!player) {
marketService->signalPromptNativePurchaseFinished(player, productId, false);
return;
}
if (RBX::Network::Player* thePlayer = RBX::Instance::fastDynamicCast<
RBX::Network::Player>(player.get())) {
RBX::Network::Player* localPlayer =
RBX::Network::Players::findLocalPlayer(
RBX::DataModel::get(player.get()));
if (localPlayer == thePlayer) {
doNativePurchaseRequest(dm, player, productId);
} else {
marketService->signalPromptNativePurchaseFinished(player,
productId, false);
}
} else {
marketService->signalPromptNativePurchaseFinished(player, productId, false);
}
}
static void playVideoAd(weak_ptr<RBX::DataModel> weakDm)
{
RBX::StandardOut::singleton()->printf(MESSAGE_INFO, "SAM in playVideoAd");
shared_ptr<RBX::DataModel> dm = weakDm.lock();
if (!dm)
return;
if (!dm.get())
return;
// Mute the audio
if(RBX::Soundscape::SoundService* soundService = RBX::ServiceProvider::find<RBX::Soundscape::SoundService>(dm.get()))
{
soundService->muteAllChannels(true);
}
// Notify the activity
JNIEnv *env = NULL;
if (gJvm->AttachCurrentThread(&env, NULL) == JNI_OK)
{
jclass callbackClass = JNI::getCallbackClass();
if (!callbackClass) {
return;
}
srand(time(NULL));
int rNum = rand() % 100 + 1;
jmethodID method;
if (rNum <= FInt::AdColonyPercentage)
{
method = JNI::getShowVideoAd_AdColonyJMethod();
if (!method)
return;
env->CallStaticVoidMethod(callbackClass, method);
if (env->ExceptionOccurred())
{
env->ExceptionDescribe();
return;
}
}
else
{
method = JNI::getShowVideoAd_GoogleJMethod();
if (!method)
return;
jstring typeString = env->NewStringUTF(FString::GoogleVideoAdUrl.c_str());
env->CallStaticVoidMethod(callbackClass, method, typeString);
if (env->ExceptionOccurred())
{
env->ExceptionDescribe();
return;
}
}
}
}
static void onVideoAdFinished(bool adShown) {
weak_ptr<RBX::Game> weakGame = PlaceLauncher::getPlaceLauncher().getCurrentGame();
if (shared_ptr<RBX::Game> game = weakGame.lock())
{
if (shared_ptr<RBX::DataModel> dm = game->getDataModel())
{
if(RBX::Soundscape::SoundService* soundService = RBX::ServiceProvider::find<RBX::Soundscape::SoundService>(dm.get()))
{
soundService->muteAllChannels(false);
}
if(RBX::AdService* adService = RBX::ServiceProvider::find<RBX::AdService>(dm.get()))
{
adService->videoAdClosed(adShown);
}
}
}
}
extern "C" {
static void setupDatamodelCallbacks(weak_ptr<RBX::DataModel> dm) {
if (RBX::UserInputService* inputService = RBX::ServiceProvider::find<
RBX::UserInputService>(dm.lock().get())) {
inputService->textBoxGainFocus.connect( boost::bind(&textBoxFocused, _1) );
inputService->motionEventListeningStarted.connect( boost::bind(&motionEventListening, _1) );
inputService->textBoxReleaseFocus.connect( boost::bind(&textBoxFocusLost, _1) );
}
if (RBX::MarketplaceService* marketplaceService =
RBX::ServiceProvider::create<RBX::MarketplaceService>(dm.lock().get())) {
//todo: standalone purchasing!
// marketplaceService->promptThirdPartyPurchaseRequested.connect(
// boost::bind(&thirdPartyPurchaseRequested, dm, _1, _2));
marketplaceService->promptNativePurchaseRequested.connect(
boost::bind(&nativePartyPurchaseRequested, dm, _1, _2));
}
if(RBX::AdService* adService = dm.lock()->find<RBX::AdService>()) {
adService->playVideoAdSignal.connect( boost::bind(playVideoAd, dm) );
}
}
// This is always called once after nativeOnStart.
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeStartGame( JNIEnv* jenv, jclass obj, jobject surface, jint jPlaceId, jint jUserId, jstring jsAccessCode,
jstring jsGameId, jint jJoinRequestType, jstring jsAssetFolderString, jfloat density, jboolean isTouchDevice, jstring josVersion, jstring jDeviceName, jstring jAppVersion, jstring model) {
FASTLOG(FLog::Android, "nativeStartGame");
RBX::SystemUtil::mOSVersion = jstringToStdString(jenv, josVersion);
RBX::SystemUtil::mDeviceName = jstringToStdString(jenv, jDeviceName);
RBX::Analytics::setReporter("Android");
RBX::Analytics::setAppVersion(jstringToStdString(jenv, jAppVersion));
jboolean isCopy;
int placeId = (int)jPlaceId;
int userId = (int)jUserId;
int joinRequestType = (int)jJoinRequestType;
lastPlaceId = placeId;
const char* accessCodeStr = jenv->GetStringUTFChars(jsAccessCode, &isCopy);
const char* gameIdStr = jenv->GetStringUTFChars(jsGameId, &isCopy);
FASTLOG1(FLog::Android, "Place ID: %d", placeId);
std::string assetFolderPath = jstringToStdString(jenv, jsAssetFolderString);
StandardOut::singleton()->printf(MESSAGE_INFO, "Asset Folder Path: %s",
assetFolderPath.c_str());
RobloxInfo::getRobloxInfo().setAssetFolderPath(assetFolderPath);
RBXASSERT( aNativeWindow == NULL );
aNativeWindow = ANativeWindow_fromSurface(jenv, surface);
FASTLOG1(FLog::Android, "Created ANativeWindow at %p", aNativeWindow);
int width = ANativeWindow_getWidth(aNativeWindow) / density;
int height = ANativeWindow_getHeight(aNativeWindow) / density;
// See ActivityGLView.java:initSurfaceView for explanation
std::string deviceModel = jstringToStdString(jenv, model);
if (deviceModel == "SM-T230NU") {
width = 960;
height = 600;
}
ANativeWindow_setBuffersGeometry(aNativeWindow, width, height, 0);
jenv->GetJavaVM(&gJvm);
StartGameParams sgp;
sgp.viewWidth = width;
sgp.viewHeight = height;
sgp.view = aNativeWindow;
sgp.placeId = placeId;
sgp.userId = userId;
sgp.accessCode = std::string(accessCodeStr, strlen(accessCodeStr));
sgp.gameId = std::string(gameIdStr, strlen(gameIdStr));
// 0 = Simple PlaceId join (placeId only) = JOIN_GAME_REQUEST_PLACEID
// 1 = Follow user join (by userId only) = JOIN_GAME_REQUEST_USERID
// 2 = Private server join (placeId & accessCode) = JOIN_GAME_REQUEST_PRIVATE_SERVER
// 3 = Game instance join (placeId & gameId) = JOIN_GAME_REQUEST_GAME_INSTANCE
sgp.joinRequestType = (JoinGameRequest)joinRequestType;
StandardOut::singleton()->printf(MESSAGE_INFO, "AccessCode: %s, length = %d", accessCodeStr, strlen(accessCodeStr));
StandardOut::singleton()->printf(MESSAGE_INFO, "GameId: %s, length = %d", gameIdStr, strlen(gameIdStr));
StandardOut::singleton()->printf(MESSAGE_INFO, "RequestTypeId: %d", sgp.joinRequestType);
sgp.assetFolderPath = assetFolderPath;
sgp.isTouchDevice = isTouchDevice;
jenv->ReleaseStringUTFChars(jsAccessCode, accessCodeStr);
jenv->ReleaseStringUTFChars(jsGameId, gameIdStr);
if (PlaceLauncher::getPlaceLauncher().startGame(sgp)) {
weak_ptr<RBX::Game> weakGame =
PlaceLauncher::getPlaceLauncher().getCurrentGame();
if (shared_ptr<RBX::Game> game = weakGame.lock()) {
if (shared_ptr<RBX::DataModel> dm = game->getDataModel()) {
setupDatamodelCallbacks(dm);
}
}
}
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativePassInput(
JNIEnv* jenv, jclass obj, jint eventId, jint xPos, jint yPos,
jint eventType, jint windowWidth, jint windowHeight) {
RobloxInput::getRobloxInput().processEvent(eventId, xPos, yPos, eventType,
windowWidth, windowHeight);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativePassPinchGesture(
JNIEnv* jenv, jclass obj, jint state, jfloat pinchDelta,
jfloat velocity, jint position1X, jint position1Y, jint position2X,
jint position2Y) {
shared_ptr<RBX::Reflection::ValueArray> touchLocations(
rbx::make_shared<RBX::Reflection::ValueArray>());
touchLocations->push_back(RBX::Vector2(position1X, position1Y));
touchLocations->push_back(RBX::Vector2(position2X, position2Y));
shared_ptr<RBX::Reflection::Tuple> args = rbx::make_shared<
RBX::Reflection::Tuple>(3);
args->values[0] = pinchDelta;
args->values[1] = velocity;
switch (state) {
case 0:
args->values[2] = RBX::InputObject::INPUT_STATE_BEGIN;
break;
case 1:
args->values[2] = RBX::InputObject::INPUT_STATE_CHANGE;
break;
case 2:
args->values[2] = RBX::InputObject::INPUT_STATE_END;
break;
default:
args->values[2] = RBX::InputObject::INPUT_STATE_NONE;
break;
}
RobloxInput::getRobloxInput().sendGestureEvent(
RBX::UserInputService::GESTURE_PINCH, args, touchLocations);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativePassTapGesture(
jint positionX, jint positionY) {
shared_ptr<RBX::Reflection::Tuple> args = rbx::make_shared<
RBX::Reflection::Tuple>(0);
shared_ptr<RBX::Reflection::ValueArray> touchLocations(
rbx::make_shared<RBX::Reflection::ValueArray>());
touchLocations->push_back(RBX::Vector2(positionX, positionY));
RobloxInput::getRobloxInput().sendGestureEvent(
RBX::UserInputService::GESTURE_TAP, args, touchLocations);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeStopGame(
JNIEnv* jenv, jclass obj) {
FASTLOG(FLog::Android, "nativeStopGame");
PlaceLauncher::getPlaceLauncher().leaveGame(true);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativePassText(
JNIEnv* jenv, jclass obj, jlong textBoxInFocus, jstring text, jboolean enterPressed, jint cursorPosition) {
std::string nativeString = jstringToStdString(jenv, text);
long textBoxPtr = (long) textBoxInFocus;
RBX::TextBox* textBoxToPass = (RBX::TextBox*) textBoxPtr;
RobloxInput::getRobloxInput().passTextInput(textBoxToPass,
nativeString.c_str(), enterPressed, int(cursorPosition));
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeReleaseFocus(
JNIEnv* jenv, jclass obj, jlong textBoxInFocus) {
long textBoxPtr = (long) textBoxInFocus;
RBX::TextBox* textBoxToPass = (RBX::TextBox*) textBoxPtr;
RobloxInput::getRobloxInput().externalReleaseFocus(textBoxToPass);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeOnLowMemory(
JNIEnv* jenv, jclass obj) {
FASTLOG(FLog::Android, "nativeOnLowMemory");
uint64_t preClearBytes = RBX::MemoryStats::usedMemoryBytes();
RobloxView* rbxView = PlaceLauncher::getPlaceLauncher().getRbxView();
rbxView->getView()->garbageCollect();
uint64_t postClearBytes = RBX::MemoryStats::usedMemoryBytes();
FASTLOG1F(FLog::Android, "PlaceLauncher::clearCachedContent: %.02fMB",
(preClearBytes - postClearBytes) / (1048576.0));
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeHandleBackPressed(
JNIEnv* jenv, jclass obj)
{
weak_ptr<RBX::Game> weakGame = PlaceLauncher::getPlaceLauncher().getCurrentGame();
if (shared_ptr<RBX::Game> game = weakGame.lock())
{
if (shared_ptr<RBX::DataModel> dm = game->getDataModel())
{
if (RBX::GuiService* guiService = RBX::ServiceProvider::find<RBX::GuiService>(dm.get()))
{
guiService->showLeaveConfirmationSignal();
}
}
}
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeInGamePurchaseFinished(JNIEnv* jenv, jclass obj, jboolean success, jlong player, jstring productId)
{
weak_ptr<RBX::Game> weakGame = PlaceLauncher::getPlaceLauncher().getCurrentGame();
if (shared_ptr<RBX::Game> game = weakGame.lock())
{
if (shared_ptr<RBX::DataModel> dm = game->getDataModel())
{
if( RBX::MarketplaceService* marketService = RBX::ServiceProvider::find<RBX::MarketplaceService>(dm.get()) )
{
std::string productIdString = jstringToStdString(jenv, productId);
long playerLong = (long) player;
RBX::Network::Player* rawPlayer = (RBX::Network::Player*) playerLong;
marketService->signalPromptNativePurchaseFinished(shared_from(rawPlayer), productIdString, success);
}
}
}
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeShutDownGraphics( JNIEnv* jenv, jobject surface, jclass obj) {
RBXASSERT( aNativeWindow != NULL );
PlaceLauncher::getPlaceLauncher().shutDownGraphics();
FASTLOG1(FLog::Android, "Destroying ANativeWindow at %p", aNativeWindow);
ANativeWindow_release( aNativeWindow );
aNativeWindow = NULL;
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeStartUpGraphics( JNIEnv* jenv, jclass obj, jobject surface, jfloat density, jstring model) {
RBXASSERT( aNativeWindow == NULL );
aNativeWindow = ANativeWindow_fromSurface(jenv, surface);
FASTLOG1(FLog::Android, "Created ANativeWindow at %p", aNativeWindow);
int width = ANativeWindow_getWidth(aNativeWindow) / density;
int height = ANativeWindow_getHeight(aNativeWindow) / density;
// See ActivityGLView.java:initSurfaceView for explanation
std::string deviceModel = jstringToStdString(jenv, model);
if (deviceModel == "SM-T230NU") {
width = 960;
height = 600;
}
// When resuming a game (i.e. coming back from the home screen or activity viewer), we sometimes pull the
// window's properties while the device is in portrait mode (this happens especially often on phones).
// If this happens, we would then recreate the GL surface with a flipped height/width.
// To stop this, we will swap the height and width if we start in portrait mode.
if (height > width)
{
int temp = width;
width = height;
height = temp;
}
ANativeWindow_setBuffersGeometry(aNativeWindow, width, height, 0);
PlaceLauncher::getPlaceLauncher().startUpGraphics( aNativeWindow, width, height );
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeVideoAdFinished( JNIEnv* jenv, jclass obj, jboolean adShown) {
onVideoAdFinished(adShown);
}
} //extern "C"
+39
View File
@@ -0,0 +1,39 @@
#ifndef JNI_GLACTIVITY_H
#define JNI_GLACTIVITY_H
#include <stdint.h>
#include <strings.h>
#include <stdlib.h>
#include <time.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include "RobloxInfo.h"
#include "FastLog.h"
#include "PlaceLauncher.h"
#include "JNIMain.h"
#include "JNIUtil.h"
#include "RobloxInput.h"
#include "RobloxView.h"
#include "util/MemoryStats.h"
#include "Util/SoundService.h"
#include "GfxBase/ViewBase.h"
#include "v8datamodel/MarketplaceService.h"
#include "v8datamodel/GuiService.h"
#include "v8datamodel/AdService.h"
#include "network/Player.h"
#include "v8datamodel/FastLogSettings.h"
namespace RBX
{
namespace JNI
{
void motionEventListening(std::string);
void textBoxFocused(shared_ptr<RBX::Instance>);
void textBoxFocusLost(shared_ptr<RBX::Instance>);
} // end JNI
} // end RBX
#endif
+234
View File
@@ -0,0 +1,234 @@
#include <stdint.h>
#include <strings.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include "RobloxInfo.h"
#include "FastLog.h"
#include "JNIMain.h"
#include "JNIUtil.h"
#include "RobloxInput.h"
#include "RobloxView.h"
#include "v8datamodel/UserInputService.h"
using namespace RBX;
using namespace RBX::JNI;
extern "C" {
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassInput(
JNIEnv* jenv, jclass obj, jint eventId, jint xPos, jint yPos,
jint eventType, jint windowWidth, jint windowHeight)
{
RobloxInput::getRobloxInput().processEvent(eventId, xPos, yPos, eventType,
windowWidth, windowHeight);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativeGamepadConnectEvent(JNIEnv* jenv, jclass obj, jint deviceId)
{
RobloxInput::getRobloxInput().handleGamepadConnect(deviceId);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativeGamepadDisconnectEvent(JNIEnv* jenv, jclass obj, jint deviceId)
{
RobloxInput::getRobloxInput().handleGamepadDisconnect(deviceId);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativeSetGamepadSupportedKey(JNIEnv* jenv, jclass obj, jint deviceId, jint keyCode, jboolean supported)
{
RobloxInput::getRobloxInput().handleGamepadKeyCodeSupportChanged(deviceId, keyCode, supported);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativeGamepadButtonEvent(JNIEnv* jenv, jclass obj, jint deviceId, jint keyCode, jint buttonState)
{
RobloxInput::getRobloxInput().handleGamepadButtonInput(deviceId, keyCode, buttonState);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativeGamepadAxisEvent(JNIEnv* jenv, jclass obj, jint deviceId, jint actionType, jfloat newValueX, jfloat newValueY, jfloat newValueZ)
{
RobloxInput::getRobloxInput().handleGamepadAxisInput(deviceId, actionType, newValueX, newValueY, newValueZ);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassPinchGesture(
JNIEnv* jenv, jclass obj, jint state, jfloat pinchScale,
jfloat velocity, jint position1X, jint position1Y, jint position2X, jint position2Y)
{
shared_ptr<RBX::Reflection::ValueArray> touchLocations(rbx::make_shared<RBX::Reflection::ValueArray>());
touchLocations->push_back(RBX::Vector2(position1X, position1Y));
touchLocations->push_back(RBX::Vector2(position2X, position2Y));
shared_ptr<RBX::Reflection::Tuple> args = rbx::make_shared<
RBX::Reflection::Tuple>(3);
args->values[0] = pinchScale;
args->values[1] = velocity;
switch (state) {
case 0:
args->values[2] = RBX::InputObject::INPUT_STATE_BEGIN;
break;
case 1:
args->values[2] = RBX::InputObject::INPUT_STATE_CHANGE;
break;
case 2:
args->values[2] = RBX::InputObject::INPUT_STATE_END;
break;
default:
args->values[2] = RBX::InputObject::INPUT_STATE_NONE;
break;
}
RobloxInput::getRobloxInput().sendGestureEvent(
RBX::UserInputService::GESTURE_PINCH, args, touchLocations);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassTapGesture(
JNIEnv* jenv, jclass obj, jint positionX, jint positionY) {
shared_ptr<RBX::Reflection::Tuple> args = rbx::make_shared<
RBX::Reflection::Tuple>(0);
shared_ptr<RBX::Reflection::ValueArray> touchLocations(
rbx::make_shared<RBX::Reflection::ValueArray>());
touchLocations->push_back(RBX::Vector2(positionX, positionY));
RobloxInput::getRobloxInput().sendGestureEvent(
RBX::UserInputService::GESTURE_TAP, args, touchLocations);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassSwipeGesture(JNIEnv* jenv, jclass obj, jint swipeDirection, jint numOfTouches)
{
shared_ptr<RBX::Reflection::ValueArray> touchLocations(rbx::make_shared<RBX::Reflection::ValueArray>());
shared_ptr<RBX::Reflection::Tuple> args = rbx::make_shared<RBX::Reflection::Tuple>(2);
switch (swipeDirection)
{
case 0:
args->values[0] = RBX::UserInputService::DIRECTION_RIGHT;
break;
case 1:
args->values[0] = RBX::UserInputService::DIRECTION_DOWN;
break;
case 2:
args->values[0] = RBX::UserInputService::DIRECTION_LEFT;
break;
case 3:
args->values[0] = RBX::UserInputService::DIRECTION_UP;
break;
default:
args->values[0] = RBX::UserInputService::DIRECTION_NONE;
break;
}
args->values[1] = numOfTouches;
RobloxInput::getRobloxInput().sendGestureEvent(RBX::UserInputService::GESTURE_SWIPE, args, touchLocations);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassLongPressGesture(JNIEnv* jenv, jclass obj, jint state, jint positionX, jint positionY)
{
shared_ptr<RBX::Reflection::ValueArray> touchLocations(rbx::make_shared<RBX::Reflection::ValueArray>());
touchLocations->push_back(RBX::Vector2(positionX, positionY));
shared_ptr<RBX::Reflection::Tuple> args = rbx::make_shared<RBX::Reflection::Tuple>(1);
switch (state)
{
case 0:
args->values[0] = RBX::InputObject::INPUT_STATE_BEGIN;
break;
case 1:
args->values[0] = RBX::InputObject::INPUT_STATE_CHANGE;
break;
case 2:
args->values[0] = RBX::InputObject::INPUT_STATE_END;
break;
default:
args->values[0] = RBX::InputObject::INPUT_STATE_NONE;
break;
}
RobloxInput::getRobloxInput().sendGestureEvent(RBX::UserInputService::GESTURE_LONGPRESS, args, touchLocations);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassPanGesture(JNIEnv* jenv, jclass obj, jint state, jint positionX, jint positionY,jfloat totalTranslationX, jfloat totalTranslationY, jfloat velocity)
{
shared_ptr<RBX::Reflection::ValueArray> touchLocations(rbx::make_shared<RBX::Reflection::ValueArray>());
touchLocations->push_back(RBX::Vector2(positionX, positionY));
RBX::Vector2 rbxTotalTranslation(totalTranslationX, totalTranslationY);
RBX::Vector2 rbxVelocity(velocity, velocity);
shared_ptr<RBX::Reflection::Tuple> args = rbx::make_shared<RBX::Reflection::Tuple>(3);
args->values[0] = rbxTotalTranslation;
args->values[1] = rbxVelocity;
switch (state)
{
case 0:
args->values[2] = RBX::InputObject::INPUT_STATE_BEGIN;
break;
case 1:
args->values[2] = RBX::InputObject::INPUT_STATE_CHANGE;
break;
case 2:
args->values[2] = RBX::InputObject::INPUT_STATE_END;
break;
default:
args->values[2] = RBX::InputObject::INPUT_STATE_NONE;
break;
}
RobloxInput::getRobloxInput().sendGestureEvent(RBX::UserInputService::GESTURE_PAN, args, touchLocations);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassRotateGesture(JNIEnv* jenv, jclass obj, jint state, jfloat rotation, jfloat velocity, jint position1X, jint position1Y, jint position2X, jint position2Y)
{
shared_ptr<RBX::Reflection::ValueArray> touchLocations(rbx::make_shared<RBX::Reflection::ValueArray>());
touchLocations->push_back(RBX::Vector2(position1X, position1Y));
touchLocations->push_back(RBX::Vector2(position2X, position2Y));
shared_ptr<RBX::Reflection::Tuple> args = rbx::make_shared<RBX::Reflection::Tuple>(3);
args->values[0] = rotation;
args->values[1] = velocity;
switch (state)
{
case 0:
args->values[2] = RBX::InputObject::INPUT_STATE_BEGIN;
break;
case 1:
args->values[2] = RBX::InputObject::INPUT_STATE_CHANGE;
break;
case 2:
args->values[2] = RBX::InputObject::INPUT_STATE_END;
break;
default:
args->values[2] = RBX::InputObject::INPUT_STATE_NONE;
break;
}
RobloxInput::getRobloxInput().sendGestureEvent(RBX::UserInputService::GESTURE_ROTATE, args, touchLocations);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassGravityChange(JNIEnv* jenv, jclass obj, jfloat x, jfloat y, jfloat z)
{
RobloxInput::getRobloxInput().sendGravityEvent(x,y,z);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassAccelerometerChange(JNIEnv* jenv, jclass obj, jfloat x, jfloat y, jfloat z)
{
RobloxInput::getRobloxInput().sendAccelerometerEvent(x,y,z);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativePassGyroscopeChange(JNIEnv* jenv, jclass obj, jfloat eulerX, jfloat eulerY, jfloat eulerZ,
jfloat quaternionX, jfloat quaternionY, jfloat quaternionZ, jfloat quaternionW)
{
RobloxInput::getRobloxInput().sendGyroscopeEvent(eulerX,eulerY,eulerZ,
quaternionX, quaternionY, quaternionZ, quaternionW);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativeSetAccelerometerEnabled(JNIEnv* jenv, jclass obj, jboolean enabled)
{
RobloxInput::getRobloxInput().setAccelerometerEnabled(enabled);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_InputListener_nativeSetGyroscopeEnabled(JNIEnv* jenv, jclass obj, jboolean enabled)
{
RobloxInput::getRobloxInput().setGyroscopeEnabled(enabled);
}
} //extern "C"
+422
View File
@@ -0,0 +1,422 @@
#include "JNIMain.h"
#include "LogManager.h"
#include "FunctionMarshaller.h"
#include "RobloxInfo.h"
#include "JNIUtil.h"
#include "util/Guid.h"
#include "util/FileSystem.h"
#include "util/standardout.h"
#include "util/RobloxGoogleAnalytics.h"
#include <exception>
#include <cstddef>
#include <curl/curl.h>
#include <pthread.h>
using namespace RBX;
FASTFLAGVARIABLE(JNIEnvScopeOptimization, false)
namespace RBX
{
namespace JNI
{
std::string exceptionReasonFilename; // set in JNIRobloxSettings.cpp
namespace // anonymous namespace
{
static JavaVM *jvm = NULL;
static jmethodID sendAppEventJMethod = 0;
static jmethodID postAppEventJMethod = 0;
static jmethodID showKeyboardJMethod = 0;
static jmethodID hideKeyboardJMethod = 0;
static jmethodID promptNativePurchaseJMethod = 0;
static jmethodID exitGameCallbackJMethod = 0;
static jmethodID exitGameWithErrorCallbackJMethod = 0;
static jmethodID showVideoAd_AdColonyJMethod = 0;
static jmethodID showVideoAd_GoogleJMethod = 0;
static jmethodID motionEventListeningJMethod = 0;
static jmethodID getApiUrlJMethod = 0;
static jclass callbackClass = 0;
static pthread_mutex_t messagesLock;
static std::deque<RBX::FunctionMarshaller::Closure*> messages;
static std::terminate_handler last_terminate_handler = NULL;
static void writeTerminateLog(const char* reason)
{
boost::filesystem::path path = FileSystem::getCacheDirectory(true, NULL);
path /= exceptionReasonFilename;
std::ofstream fout(path.c_str());
fout << reason;
fout.flush();
}
static void terminateHandler()
{
try
{
throw;
}
catch (const std::exception& e)
{
writeTerminateLog(e.what());
StandardOut::singleton()->printf(MESSAGE_ERROR, "Exception thrown: %s", e.what());
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "Uncaught Exception", e.what(), 0, true);
}
catch (...)
{
writeTerminateLog("Unknown exception");
StandardOut::singleton()->printf(MESSAGE_ERROR, "Unknown exception");
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "Uncaught Exception", "Unknown exception", 0, true);
}
abort();
}
static pthread_key_t getEnvKey;
static pthread_once_t getEnvOnce = PTHREAD_ONCE_INIT;
static void getEnvDtor(void* p)
{
if (JNIEnv* env = static_cast<JNIEnv*>(p))
{
JavaVM *jvm = 0;
env->GetJavaVM(&jvm);
jint ret = jvm->DetachCurrentThread();
RBXASSERT(ret == JNI_OK);
}
}
static void getEnvKeyCreate()
{
pthread_key_create(&getEnvKey, getEnvDtor);
}
static JNIEnv* getEnvForCurrentThread()
{
pthread_once(&getEnvOnce, getEnvKeyCreate);
if (void* p = pthread_getspecific(getEnvKey))
return static_cast<JNIEnv*>(p);
JNIEnv* env = NULL;
jint ret = jvm->AttachCurrentThread(&env, NULL);
RBXASSERT(ret == JNI_OK);
// Register for detach when thread exits
pthread_setspecific(getEnvKey, env);
return env;
}
class JNIEnvScope
{
public:
JNIEnv* env;
bool wasAttached;
JNIEnvScope( void )
{
env = NULL;
wasAttached = false;
if (FFlag::JNIEnvScopeOptimization)
{
env = getEnvForCurrentThread();
return;
}
jint ret = jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
switch(ret)
{
// UI thread.
case JNI_OK:
break;
// All other threads.
case JNI_EDETACHED:
{
// Make up a name to quell: W/art ... attached without supplying a name.
char buf[20];
sprintf(buf,"%x",gettid());
JavaVMAttachArgs args = { JNI_VERSION_1_6, buf, NULL };
jint isOk = jvm->AttachCurrentThread(&env, &args) == JNI_OK;
RBXASSERT(isOk);
wasAttached = true;
break;
}
default:
RBXASSERT(0);
break;
}
}
~JNIEnvScope( void )
{
if( wasAttached )
{
jvm->DetachCurrentThread();
}
}
};
jint onLoad(JavaVM *vm, void *reserved)
{
last_terminate_handler = std::set_terminate(&terminateHandler);
JNI::jvm = vm;
JNIEnvScope scope;
jclass c = scope.env->FindClass("wtf/watrbx/client/ActivityGlView");
JNI::callbackClass = (jclass)scope.env->NewGlobalRef(c);
JNI::sendAppEventJMethod = scope.env->GetStaticMethodID( c, "sendAppEvent", "(Z)V");
JNI::postAppEventJMethod = scope.env->GetStaticMethodID( c, "postAppEvent", "()V");
JNI::showKeyboardJMethod = scope.env->GetStaticMethodID( c, "showKeyboard", "(JLjava/lang/String;)V");
JNI::hideKeyboardJMethod = scope.env->GetStaticMethodID( c, "hideKeyboard", "()V");
JNI::showVideoAd_AdColonyJMethod = scope.env->GetStaticMethodID( c, "showAdColonyAd", "()V");
JNI::showVideoAd_GoogleJMethod = scope.env->GetStaticMethodID( c, "showGoogleAd", "(Ljava/lang/String;)V");
JNI::promptNativePurchaseJMethod = scope.env->GetStaticMethodID( c, "promptNativePurchase", "(JLjava/lang/String;Ljava/lang/String;)V");
JNI::exitGameCallbackJMethod = scope.env->GetStaticMethodID( c, "exitGame", "()V");
JNI::exitGameWithErrorCallbackJMethod = scope.env->GetStaticMethodID( c, "exitGameWithError", "(Ljava/lang/String;)V");
JNI::motionEventListeningJMethod = scope.env->GetStaticMethodID( c, "listenToMotionEvents", "(Ljava/lang/String;)V");
JNI::getApiUrlJMethod = scope.env->GetStaticMethodID( c, "getApiUrl", "()Ljava/lang/String;");
int pthreadError = ::pthread_mutex_init(&JNI::messagesLock, NULL);
RBXASSERT( pthreadError==0 );
// libcurl must be initialized while no other threads are running
curl_global_init(CURL_GLOBAL_DEFAULT | CURL_GLOBAL_ACK_EINTR);
return JNI_VERSION_1_6;
}
void onUnload(JavaVM *vm, void *reserved)
{
JNIEnvScope scope;
scope.env->DeleteGlobalRef( callbackClass );
int pthreadError = ::pthread_mutex_destroy( &JNI::messagesLock );
RBXASSERT( pthreadError==0 );
JNI::jvm = NULL;
// libcurl must be cleaned up while no other threads are running
curl_global_cleanup();
StandardOut::singleton()->printf(MESSAGE_INFO, "Tearing down FastLog.");
JNI::LogManager::tearDownFastLog(); // this is setup in JNIRobloxSettings.cpp
std::set_terminate(last_terminate_handler);
}
void pushMessage( RBX::FunctionMarshaller::Closure* pClosure )
{
int pthreadError = 0;
pthreadError |= pthread_mutex_lock(&messagesLock);
messages.push_back( pClosure );
pthreadError |= pthread_mutex_unlock(&messagesLock);
RBXASSERT( pthreadError==0 );
}
RBX::FunctionMarshaller::Closure* popMessage( void )
{
RBX::FunctionMarshaller::Closure* p = NULL;
int pthreadError = 0;
pthreadError |= pthread_mutex_lock(&messagesLock);
if( !messages.empty() )
{
p = messages.front();
messages.pop_front();
}
else
{
p = NULL;
}
pthreadError |= pthread_mutex_unlock(&messagesLock);
RBXASSERT( pthreadError==0 );
return p;
}
void callMessage( RBX::FunctionMarshaller::Closure* closure )
{
RBX::CEvent *pWaitEvent = closure->waitEvent;
try
{
boost::function<void()>* pF = closure->f;
(*pF)();
delete pF;
delete closure;
}
catch (RBX::base_exception& e)
{
StandardOut::singleton()->printf(MESSAGE_ERROR, e.what());
closure->errorMessage = e.what();
}
// If a task is waiting on an event, set it
if (pWaitEvent)
{
pWaitEvent->Set();
}
}
} // anonymous namespace
// Send callback to the main thread, possibly waiting for it.
void sendAppEvent( void* pClosure )
{
RBX::FunctionMarshaller::Closure* closure = (RBX::FunctionMarshaller::Closure*)pClosure;
RBX::CEvent* waitEvent = closure->waitEvent; // closure gets deleted.
pushMessage(closure);
// NOTE: A null wait event means the caller must wait instead. This is done in Java.
jboolean waitFlag = (waitEvent == NULL);
JNIEnvScope scope;
scope.env->CallStaticVoidMethod(callbackClass, sendAppEventJMethod, waitFlag);
if (scope.env->ExceptionCheck()) {
StandardOut::singleton()->printf(MESSAGE_ERROR, "sendAppEvent exception");
scope.env->ExceptionDescribe();
scope.env->ExceptionClear();
}
if(waitEvent != NULL)
{
waitEvent->Wait();
}
}
// Send callback to the main thread, not waiting for it.
void postAppEvent( void* pClosure )
{
RBX::FunctionMarshaller::Closure* closure = (RBX::FunctionMarshaller::Closure*)pClosure;
pushMessage(closure);
JNIEnvScope scope;
scope.env->CallStaticVoidMethod(callbackClass, postAppEventJMethod);
if (scope.env->ExceptionCheck()) {
StandardOut::singleton()->printf(MESSAGE_ERROR, "postAppEvent exception!");
scope.env->ExceptionDescribe();
scope.env->ExceptionClear();
}
}
// Wait for all main thread callbacks to complete.
void processAppEvents( void )
{
while (RBX::FunctionMarshaller::Closure* closure = popMessage())
{
callMessage(closure);
}
}
jclass getCallbackClass()
{
return JNI::callbackClass;
}
jmethodID getMotionEventListeningJMethod()
{
return JNI::motionEventListeningJMethod;
}
jmethodID getShowKeyboardJMethod()
{
return JNI::showKeyboardJMethod;
}
jmethodID getHideKeyboardJMethod()
{
return JNI::hideKeyboardJMethod;
}
jmethodID getPromptNativePurchaseJMethod()
{
return JNI::promptNativePurchaseJMethod;
}
jmethodID getShowVideoAd_AdColonyJMethod()
{
return JNI::showVideoAd_AdColonyJMethod;
}
jmethodID getShowVideoAd_GoogleJMethod()
{
return JNI::showVideoAd_GoogleJMethod;
}
void exitGameWithError(std::string errId)
{
JNI::JNIEnvScope scope;
jstring errorjs = scope.env->NewStringUTF(errId.c_str());
scope.env->CallStaticVoidMethod(callbackClass, exitGameWithErrorCallbackJMethod , errorjs);
}
void exitGame( void )
{
JNI::JNIEnvScope scope;
scope.env->CallStaticVoidMethod(callbackClass, exitGameCallbackJMethod);
}
void handleBackPressed( void )
{
exitGame();
}
std::string getApiUrl() {
JNIEnvScope scope;
jstring url = (jstring)scope.env->CallStaticObjectMethod(callbackClass, getApiUrlJMethod);
if (scope.env->ExceptionCheck()) {
jclass throwable_class = scope.env->FindClass("java/lang/Throwable");
jmethodID mid_throwable_toString = scope.env->GetMethodID(throwable_class, "toString", "()Ljava/lang/String;");
jstring msg_obj = (jstring) scope.env->CallObjectMethod(scope.env->ExceptionOccurred(), mid_throwable_toString);
StandardOut::singleton()->printf(MESSAGE_INFO, "getApiUrl exception: %s", jstringToStdString(scope.env, msg_obj).c_str());
scope.env->ExceptionClear();
return "";
} else {
return jstringToStdString(scope.env, url);
}
}
} // JNI
} // RBX
extern "C" {
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
return JNI::onLoad( vm, reserved );
}
void JNI_OnUnload(JavaVM *vm, void *reserved)
{
JNI::onUnload( vm, reserved );
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_ActivityGlView_nativeCallMessagesFromMainThread(JNIEnv* jenv, jclass obj )
{
JNI::processAppEvents();
}
} // extern "C"
+36
View File
@@ -0,0 +1,36 @@
#ifndef JNI_MAIN_H
#define JNI_MAIN_H
#include <jni.h>
#include <string>
namespace RBX
{
namespace JNI
{
void sendAppEvent( void* pClosure );
void postAppEvent( void* pClosure );
void processAppEvents( void );
jclass getCallbackClass();
jmethodID getShowKeyboardJMethod();
jmethodID getHideKeyboardJMethod();
jmethodID getPromptNativePurchaseJMethod();
jmethodID getShowVideoAd_AdColonyJMethod();
jmethodID getShowVideoAd_GoogleJMethod();
jmethodID getMotionEventListeningJMethod();
// TODO: Have this actually hooked by the engine.
void handleBackPressed( void );
void exitGame( void );
void exitGameWithError( std::string errId );
std::string getApiUrl();
} // JNI
} // RBX
#endif // JNI_MAIN_H
+148
View File
@@ -0,0 +1,148 @@
/*
* Copyright (c) 1983, 1991, 1993, 2001
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name of the University 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 REGENTS 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 REGENTS 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.
*/
#ifndef gmon_h
#define gmon_h
/* Size of the 4.4BSD gmon header */
#define GMON_HDRSIZE_BSD44_32 (4 + 4 + 4 + 4 + 4 + (3 * 4))
#define GMON_HDRSIZE_BSD44_64 (8 + 8 + 4 + 4 + 4 + (3 * 4))
#if 0 /* For documentation purposes only. */
struct raw_phdr
{
char low_pc[sizeof(void *)]; /* base pc address of sample buffer */
char high_pc[sizeof(void *)];/* max pc address of sampled buffer */
char ncnt[4]; /* size of sample buffer (plus this
header) */
char version[4]; /* version number */
char profrate[4]; /* profiling clock rate */
char spare[3*4]; /* reserved */
};
#endif
#define GMONVERSION 0x00051879
/* Size of the old BSD gmon header */
#define GMON_HDRSIZE_OLDBSD_32 (4 + 4 + 4)
/* FIXME: Checking host compiler defines here means that we can't
use a cross gprof alpha OSF. */
#if defined(__alpha__) && defined (__osf__)
#define GMON_HDRSIZE_OLDBSD_64 (8 + 8 + 4 + 4)
#else
#define GMON_HDRSIZE_OLDBSD_64 (8 + 8 + 4)
#endif
#if 0 /* For documentation purposes only. */
struct old_raw_phdr
{
char low_pc[sizeof(void *)]; /* base pc address of sample buffer */
char high_pc[sizeof(void *)];/* max pc address of sampled buffer */
char ncnt[4]; /* size of sample buffer (plus this
header) */
#if defined (__alpha__) && defined (__osf__)
/*
* DEC's OSF v3.0 uses 4 bytes of padding to bring the header to
* a size that is a multiple of 8.
*/
char pad[4];
#endif
};
#endif
/*
* Histogram counters are unsigned shorts:
*/
#define HISTCOUNTER unsigned short
/*
* Fraction of text space to allocate for histogram counters here, 1/2:
*/
#define HISTFRACTION 2
/*
* Fraction of text space to allocate for from hash buckets. The
* value of HASHFRACTION is based on the minimum number of bytes of
* separation between two subroutine call points in the object code.
* Given MIN_SUBR_SEPARATION bytes of separation the value of
* HASHFRACTION is calculated as:
*
* HASHFRACTION = MIN_SUBR_SEPARATION / (2 * sizeof(short) - 1);
*
* For the VAX, the shortest two call sequence is:
*
* calls $0,(r0)
* calls $0,(r0)
*
* which is separated by only three bytes, thus HASHFRACTION is
* calculated as:
*
* HASHFRACTION = 3 / (2 * 2 - 1) = 1
*
* Note that the division above rounds down, thus if MIN_SUBR_FRACTION
* is less than three, this algorithm will not work!
*/
#define HASHFRACTION 1
/*
* Percent of text space to allocate for tostructs with a minimum:
*/
#define ARCDENSITY 2
#define MINARCS 50
struct tostruct
{
char *selfpc;
int count;
unsigned short link;
};
/*
* A raw arc, with pointers to the calling site and the called site
* and a count. Everything is defined in terms of characters so
* as to get a packed representation (otherwise, different compilers
* might introduce different padding):
*/
#if 0 /* For documentation purposes only. */
struct raw_arc
{
char from_pc[sizeof(void *)];
char self_pc[sizeof(void *)];
char count[sizeof(long)];
};
#endif
/*
* General rounding functions:
*/
#define ROUNDDOWN(x,y) (((x)/(y))*(y))
#define ROUNDUP(x,y) ((((x)+(y)-1)/(y))*(y))
#endif /* gmon_h */
+45
View File
@@ -0,0 +1,45 @@
/* gmon_out.h
Copyright 2000, 2001 Free Software Foundation, Inc.
This file is part of GNU Binutils.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/* A gmon.out file consists of a header (defined by gmon_hdr) followed
by a sequence of records. Each record starts with a one-byte tag
identifying the type of records, followed by records specific data. */
#ifndef gmon_out_h
#define gmon_out_h
#define GMON_MAGIC "gmon" /* magic cookie */
#define GMON_VERSION 1 /* version number */
/* Raw header as it appears on file (without padding). */
struct gmon_hdr
{
char cookie[4];
char version[4];
char spare[3 * 4];
};
/* Types of records in this file. */
typedef enum
{
GMON_TAG_TIME_HIST = 0, GMON_TAG_CG_ARC = 1, GMON_TAG_BB_COUNT = 2
}
GMON_Record_Tag;
#endif /* gmon_out_h */
@@ -0,0 +1,28 @@
/*
* Part of the android-ndk-profiler library.
* Copyright (C) Richard Quirk
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
.align 2
.thumb_func
.global __gnu_mcount_nc
.type __gnu_mcount_nc,function
__gnu_mcount_nc:
push {r0-r3}
push {lr}
ldr r0, [sp, #20] @ r0 = lr pushed by calling routine
mov r1, lr @ address of calling routine
bl profCount
pop {r2} @ this routine's return address
pop {r0, r1}
@ stack contains r2, r3 and lr
ldr r3, [sp , #8] @ r3 = lr pushed by calling routine
str r2, [sp, #8] @ return address now last on the stack
mov lr, r3 @ lr = caller's expected lr
pop {r2, r3}
pop {pc} @ pop caller's expected r2, r3 and return
+482
View File
@@ -0,0 +1,482 @@
/* adapted from VisualBoyAdvance, which had the following notice.*/
/* VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
* Copyright (C) 1999-2003 Forgotten
* Copyright (C) 2004 Forgotten and the VBA development team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or(at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/* adapted from gmon.c */
/*-
* Copyright (c) 1991, 1998 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. [rescinded 22 July 1999]
* 4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
*/
#include <android/log.h> /* for __android_log_print, ANDROID_LOG_INFO, etc */
#include <errno.h> /* for errno */
#include <signal.h> /* for sigaction, etc */
#include <stdint.h> /* for uint32_t, uint16_t, etc */
#include <stdio.h> /* for FILE */
#include <stdlib.h> /* for getenv */
#include <sys/time.h> /* for setitimer, etc */
#include "gmon.h"
#include "gmon_out.h"
#include "prof.h"
#include "read_maps.h"
#include "ucontext.h" /* for ucontext_t */
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "PROFILING", __VA_ARGS__)
#define FREQ_HZ 100
#define DEFAULT_GMON_OUT "/sdcard/gmon.out"
/*
* froms is actually a bunch of unsigned shorts indexing tos
*/
static int profiling = 3;
static unsigned short *froms;
static struct tostruct *tos = 0;
static long tolimit = 0;
static uint32_t s_lowpc = 0;
static uint32_t s_highpc = 0;
static unsigned long s_textsize = 0;
static int ssiz;
static char *sbuf;
static int s_scale;
static int hist_num_bins = 0;
static char hist_dimension[16] = "seconds";
static char hist_dimension_abbrev = 's';
static struct proc_map *s_maps = NULL;
static int s_freq_hz = FREQ_HZ;
static void systemMessage(int a, const char *msg)
{
LOGI("%d: %s", a, msg);
}
static void profPut32(char *b, uint32_t v)
{
b[0] = v & 255;
b[1] = (v >> 8) & 255;
b[2] = (v >> 16) & 255;
b[3] = (v >> 24) & 255;
}
static void profPut16(char *b, uint16_t v)
{
b[0] = v & 255;
b[1] = (v >> 8) & 255;
}
static int profWrite8(FILE *f, uint8_t b)
{
if (fwrite(&b, 1, 1, f) != 1) {
return 1;
}
return 0;
}
static int profWrite32(FILE *f, uint32_t v)
{
char buf[4];
profPut32(buf, v);
if (fwrite(buf, 1, 4, f) != 4) {
return 1;
}
return 0;
}
static int profWrite(FILE *f, char *buf, unsigned int n)
{
if (fwrite(buf, 1, n, f) != n) {
return 1;
}
return 0;
}
static void check_profil(uint32_t frompcindex)
{
if (sbuf && ssiz) {
uint16_t *b = (uint16_t *)sbuf;
int pc = (frompcindex - s_lowpc) / s_scale;
if(pc >= 0 && pc < ssiz)
b[pc]++;
}
}
static void profile_action(int sig, siginfo_t *info, void *context)
{
ucontext_t *ucontext = (ucontext_t*) context;
struct sigcontext *mcontext = &ucontext->uc_mcontext;
if (profiling)
return;
check_profil(mcontext->arm_pc);
}
static void add_profile_handler(void)
{
struct sigaction action;
/* request info, sigaction called instead of sighandler */
action.sa_flags = SA_SIGINFO | SA_RESTART;
action.sa_sigaction = profile_action;
sigemptyset(&action.sa_mask);
int result = sigaction(SIGPROF, &action, NULL);
if (result != 0) {
/* panic */
LOGI("add_profile_handler, sigaction failed %d %d", result, errno);
return;
}
struct itimerval timer;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 1000000 / s_freq_hz;
timer.it_value = timer.it_interval;
setitimer(ITIMER_PROF, &timer, 0);
}
static void remove_profile_handler(void)
{
struct itimerval timer;
memset(&timer, 0, sizeof(timer));
setitimer(ITIMER_PROF, &timer, 0);
}
/* Control profiling;
profiling is what mcount checks to see if
all the data structures are ready. */
static void profControl(int mode)
{
if (mode) {
/* start */
add_profile_handler();
profiling = 0;
} else {
remove_profile_handler();
/* stop */
profiling = 3;
systemMessage(1, "parent: done profiling");
}
}
static void get_frequency(void)
{
char *freq = getenv("CPUPROFILE_FREQUENCY");
if (freq != 0) {
int freqval = strtol(freq, 0, 0);
if (freqval > 0)
s_freq_hz = freqval;
else
LOGI("Invalid frequency value: %d", freqval);
}
}
#define MSG ("No space for profiling buffer(s)\n")
__attribute__((visibility("default")))
void monstartup(const char *libname)
{
int monsize;
char *buffer;
uint32_t lowpc, highpc;
FILE *self = fopen("/proc/self/maps", "r");
s_maps = read_maps(self, libname);
if (s_maps == NULL) {
systemMessage(0, "No maps found");
return;
}
lowpc = s_maps->lo;
highpc = s_maps->hi;
__android_log_print(ANDROID_LOG_INFO, "PROFILING",
"Profile %s %x-%x: %d",
libname,
lowpc, highpc,
s_maps->base);
get_frequency();
/*
* round lowpc and highpc to multiples of the density we're using
* so the rest of the scaling (here and in gprof) stays in ints.
*/
lowpc = ROUNDDOWN(lowpc, HISTFRACTION * sizeof(HISTCOUNTER));
s_lowpc = lowpc;
highpc = ROUNDUP(highpc, HISTFRACTION * sizeof(HISTCOUNTER));
s_highpc = highpc;
s_textsize = highpc - lowpc;
monsize = (s_textsize / HISTFRACTION);
s_scale = HISTFRACTION;
buffer = calloc(1, 2 * monsize);
if (buffer == NULL) {
systemMessage(0, MSG);
return;
}
froms = calloc(1, 4 * s_textsize / HASHFRACTION);
if (froms == NULL) {
systemMessage(0, MSG);
free(buffer);
buffer = NULL;
return;
}
tolimit = s_textsize * ARCDENSITY / 100;
if (tolimit < MINARCS) {
tolimit = MINARCS;
} else if (tolimit > 65534) {
tolimit = 65534;
}
tos =
(struct tostruct *) calloc(1,
tolimit * sizeof(struct tostruct));
if (tos == NULL) {
systemMessage(0, MSG);
free(buffer);
buffer = NULL;
free(froms);
froms = NULL;
return;
}
tos[0].link = 0;
sbuf = buffer;
ssiz = monsize;
if (monsize <= 0) {
return;
}
profControl(1);
}
static const char *get_gmon_out(void)
{
char *gmon_out = getenv("CPUPROFILE");
if (gmon_out && strlen(gmon_out))
return gmon_out;
return DEFAULT_GMON_OUT;
}
__attribute__((visibility("default")))
void moncleanup(void)
{
FILE *fd;
int fromindex;
int endfrom;
uint32_t frompc;
int toindex;
struct gmon_hdr ghdr;
const char *gmon_name = get_gmon_out();
LOGI("parent: moncleanup called");
profControl(0);
LOGI("writing gmon.out");
fd = fopen(gmon_name, "wb");
if (fd == NULL) {
systemMessage(0, "mcount: gmon.out");
return;
}
memcpy(&ghdr.cookie[0], GMON_MAGIC, 4);
profPut32((char *) ghdr.version, GMON_VERSION);
if (fwrite(&ghdr, sizeof(ghdr), 1, fd) != 1) {
systemMessage(0, "mcount: gmon.out header");
fclose(fd);
return;
}
hist_num_bins = ssiz;
if (profWrite8(fd, GMON_TAG_TIME_HIST) ||
profWrite32(fd, get_real_address(s_maps, (uint32_t) s_lowpc)) ||
profWrite32(fd, get_real_address(s_maps, (uint32_t) s_highpc)) ||
profWrite32(fd, hist_num_bins) ||
profWrite32(fd, s_freq_hz) ||
profWrite(fd, hist_dimension, 15) ||
profWrite(fd, &hist_dimension_abbrev, 1)) {
systemMessage(0, "mcount: gmon.out hist");
fclose(fd);
return;
}
uint16_t *hist_sample = (uint16_t *) sbuf;
uint16_t count;
int i;
for (i = 0; i < hist_num_bins; ++i) {
profPut16((char *) &count, hist_sample[i]);
if (fwrite(&count, sizeof(count), 1, fd) != 1) {
systemMessage(0, "mcount: gmon.out sample");
fclose(fd);
return;
}
}
endfrom = s_textsize / (HASHFRACTION * sizeof(*froms));
for (fromindex = 0; fromindex < endfrom; fromindex++) {
if (froms[fromindex] == 0) {
continue;
}
frompc =
s_lowpc + (fromindex * HASHFRACTION * sizeof(*froms));
frompc = get_real_address(s_maps, frompc);
for (toindex = froms[fromindex]; toindex != 0;
toindex = tos[toindex].link) {
if (profWrite8(fd, GMON_TAG_CG_ARC)
|| profWrite32(fd, (uint32_t) frompc)
|| profWrite32(fd,
get_real_address(s_maps,
(uint32_t)
tos[toindex].
selfpc))
|| profWrite32(fd, tos[toindex].count)) {
systemMessage(0, "mcount: arc");
fclose(fd);
return;
}
}
}
fclose(fd);
}
void profCount(unsigned short *frompcindex, char *selfpc)
{
struct tostruct *top;
struct tostruct *prevtop;
long toindex;
/*
* find the return address for mcount,
* and the return address for mcount's caller.
*/
/* selfpc = pc pushed by mcount call.
This identifies the function that was just entered. */
/*selfpc = (char *) reg[14].I; */
/* frompcindex = pc in preceding frame.
This identifies the caller of the function just entered. */
/*frompcindex = (unsigned short *) reg[12].I; */
/*
* check that we are profiling
* and that we aren't recursively invoked.
*/
if (profiling) {
return;
}
profiling++;
/*
* check that frompcindex is a reasonable pc value.
* for example: signal catchers get called from the stack,
* not from text space. too bad.
*/
frompcindex =
(unsigned short *)((long) frompcindex - (long) s_lowpc);
if ((unsigned long) frompcindex > s_textsize) {
goto done;
}
frompcindex =
&froms[((long) frompcindex) / (HASHFRACTION * sizeof(*froms))];
toindex = *frompcindex;
if (toindex == 0) {
/*
* first time traversing this arc
*/
toindex = ++tos[0].link;
if (toindex >= tolimit) {
goto overflow;
}
*frompcindex = (unsigned short) toindex;
top = &tos[toindex];
top->selfpc = selfpc;
top->count = 1;
top->link = 0;
goto done;
}
top = &tos[toindex];
if (top->selfpc == selfpc) {
/*
* arc at front of chain; usual case.
*/
top->count++;
goto done;
}
/*
* have to go looking down chain for it.
* top points to what we are looking at,
* prevtop points to previous top.
* we know it is not at the head of the chain.
*/
for (; /* goto done */ ;) {
if (top->link == 0) {
/*
* top is end of the chain and none of the chain
* had top->selfpc == selfpc.
* so we allocate a new tostruct
* and link it to the head of the chain.
*/
toindex = ++tos[0].link;
if (toindex >= tolimit) {
goto overflow;
}
top = &tos[toindex];
top->selfpc = selfpc;
top->count = 1;
top->link = *frompcindex;
*frompcindex = (unsigned short) toindex;
goto done;
}
/*
* otherwise, check the next arc on the chain.
*/
prevtop = top;
top = &tos[top->link];
if (top->selfpc == selfpc) {
/*
* there it is.
* increment its count
* move it to the head of the chain.
*/
top->count++;
toindex = prevtop->link;
prevtop->link = top->link;
top->link = *frompcindex;
*frompcindex = (unsigned short) toindex;
goto done;
}
}
done:
profiling--;
/* and fall through */
out:
return; /* normal return restores saved registers */
overflow:
profiling++; /* halt further profiling */
#define TOLIMIT "mcount: tos overflow\n"
systemMessage(0, TOLIMIT);
goto out;
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Part of the android-ndk-profiler library.
* Copyright (C) Richard Quirk
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
#ifndef prof_h_seen
#define prof_h_seen
#ifdef __cplusplus
extern "C" {
#endif
void monstartup(const char *libname);
void moncleanup(void);
#ifdef __cplusplus
}
#endif
#endif
+69
View File
@@ -0,0 +1,69 @@
/*
* Part of the android-ndk-profiler library.
* Copyright (C) Richard Quirk
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "read_maps.h"
static char s_line[256];
void free_maps(struct proc_map *s)
{
struct proc_map *next = s->next;
while (next != NULL) {
struct proc_map *tmp = next;
next = next->next;
free(tmp);
}
free(s);
}
struct proc_map *read_maps(FILE *fp, const char *lname)
{
struct proc_map *results = NULL;
struct proc_map *current = NULL;
size_t namelen = strlen(lname);
while (fgets(s_line, sizeof(s_line), fp) != NULL) {
size_t len = strlen(s_line);
len--;
s_line[len] = 0;
if (namelen < len && strcmp(lname, &s_line[len - namelen]) == 0) {
char c[1];
char perm[4];
int lo, base, hi;
sscanf(s_line, "%x-%x %4c %x %c", &lo, &hi, perm, &base, c);
if (results == NULL) {
current = malloc(sizeof(struct proc_map));
current->next = NULL;
results = current;
} else {
current->next = malloc(sizeof(struct proc_map));
current = current->next;
current->next = NULL;
}
current->base = base;
current->lo = lo;
current->hi = hi;
}
}
return results;
}
unsigned int get_real_address(const struct proc_map *maps, unsigned int fake)
{
const struct proc_map *mp = maps;
while (mp) {
if (fake >= mp->lo && fake <= mp->hi) {
return fake - mp->lo;
}
mp = mp->next;
}
return fake;
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Part of the android-ndk-profiler library.
* Copyright (C) Richard Quirk
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
#ifndef read_maps_h_seen
#define read_maps_h_seen
struct proc_map {
unsigned int base;
unsigned int lo;
unsigned int hi;
struct proc_map *next;
};
struct proc_map *read_maps(FILE *fp, const char *lname);
void free_maps(struct proc_map *s);
unsigned int get_real_address(const struct proc_map *maps, unsigned int fake);
#endif
+24
View File
@@ -0,0 +1,24 @@
/*
* Part of the android-ndk-profiler library.
* Copyright (C) Richard Quirk
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
#ifndef ucontext_h_seen
#define ucontext_h_seen
#include <asm/sigcontext.h> /* for sigcontext */
#include <asm/signal.h> /* for stack_t */
typedef struct ucontext {
unsigned long uc_flags;
struct ucontext *uc_link;
stack_t uc_stack;
struct sigcontext uc_mcontext;
unsigned long uc_sigmask;
} ucontext_t;
#endif
+146
View File
@@ -0,0 +1,146 @@
#include "JNIUtil.h"
#include <string>
#include <jni.h>
#include "LogManager.h"
#include "FastLog.h"
#include "util/Http.h"
#include "util/Statistics.h"
#include "util/standardout.h"
#include "util/Http.h"
#include "util/FileSystem.h"
#include "v8datamodel/GuiBuilder.h"
#include "JNIProfiler/prof.h"
LOGGROUP(Android)
using namespace RBX;
using namespace RBX::JNI;
namespace RBX
{
namespace JNI
{
extern std::string fileSystemCacheDir; // FileSystem.cpp
extern std::string fileSystemFilesDir; // FileSystem.cpp
extern std::string platformUserAgent; // LogManager.cpp
extern std::string robloxVersion; // LogManager.cpp
extern std::string exceptionReasonFilename; // JNIMain.cpp
} // namespace JNI
} // namespace RBX
extern "C"
{
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetBaseUrl(JNIEnv* jenv, jclass obj, jstring jsBaseUrl)
{
std::string baseUrl = jstringToStdString(jenv, jsBaseUrl);
FASTLOGS(FLog::Android, "Base URL: %s", baseUrl.c_str());
SetBaseURL(baseUrl);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetCacheDirectory(JNIEnv* jenv, jclass obj, jstring jsCacheDirectory)
{
fileSystemCacheDir = jstringToStdString(jenv, jsCacheDirectory);
FASTLOGS(FLog::Android, "Cache Directory: %s", fileSystemCacheDir.c_str());
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetFilesDirectory(JNIEnv* jenv, jclass obj, jstring jsFilesDirectory)
{
fileSystemFilesDir = jstringToStdString(jenv, jsFilesDirectory);
FASTLOGS(FLog::Android, "Files Directory: %s", fileSystemFilesDir.c_str());
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetExceptionReasonFilename(JNIEnv* jenv, jclass obj, jstring jsExceptionReasonFilename)
{
exceptionReasonFilename = jstringToStdString(jenv, jsExceptionReasonFilename);
FASTLOGS(FLog::Android, "Exception reason filename (for terminate_handler): %s", exceptionReasonFilename.c_str());
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeInitFastLog(JNIEnv* jenv, jclass obj)
{
if (fileSystemCacheDir.empty())
{
throw RBX::runtime_error("Cannot initialize fastlog system. Cache directory not set.");
}
StandardOut::singleton()->printf(MESSAGE_INFO, "Setting up fast log system.");
LogManager::setupFastLog();
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeInitBreakpad(JNIEnv* jenv, jclass obj, jboolean cleanup)
{
if (GetBaseURL().empty() || fileSystemCacheDir.empty())
{
throw RBX::runtime_error("Cannot initialize breakpad. Base URL or cache directory not set.");
}
LogManager::setupBreakpad(cleanup);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetPlatformUserAgent(JNIEnv* jenv, jclass obj, jstring jsUserAgent)
{
platformUserAgent = jstringToStdString(jenv, jsUserAgent);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetRobloxVersion(JNIEnv* jenv, jclass obj, jstring jsRobloxVersion)
{
robloxVersion = jstringToStdString(jenv, jsRobloxVersion);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetHttpProxy(JNIEnv* jenv, jclass obj, jstring jsProxyPort, jlong proxyPort)
{
std::string proxyHost = jstringToStdString(jenv, jsProxyPort);
Http::setProxy(proxyHost, proxyPort);
FASTLOGS(FLog::Android, "Set proxy host to %s", proxyHost.c_str());
FASTLOG1(FLog::Android, "Set proxy port to %ld", proxyPort);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetCookiesForDomain(JNIEnv* jenv, jclass obj, jstring jsDomain, jstring jsCookies)
{
std::string domain = jstringToStdString(jenv, jsDomain);
std::string cookies = jstringToStdString(jenv, jsCookies);
FASTLOGS(FLog::Android, "Setting ROBLOX cookies: %s", cookies.c_str());
Http::setCookiesForDomain(domain, cookies);
}
JNIEXPORT void JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeSetDebugDisplay(JNIEnv* jenv, jclass obj, jint debugDisplayMode)
{
RBX::GuiBuilder::setDebugDisplay(static_cast<RBX::GuiBuilder::Display>(debugDisplayMode));
}
// Sending down 0 writes out the data. See https://code.google.com/p/android-ndk-profiler/
JNIEXPORT jboolean JNICALL Java_wtf_watrbx_client_RobloxSettings_nativeEnableNDKProfiler(JNIEnv* jenv, jclass obj, jint frequency)
{
// This is GPL code and cannot be shipped.
#if 0
if( frequency > 0 )
{
// Log as error as this stuff is not supposed to ship.
char buf[512];
sprintf( buf, "%d", frequency );
setenv("CPUPROFILE_FREQUENCY", buf, 1);
StandardOut::singleton()->printf(MESSAGE_ERROR, "PROFILING CPUPROFILE_FREQUENCY: %s", buf);
sprintf( buf, "%s/gmon.out", fileSystemFilesDir.c_str() );
setenv("CPUPROFILE", buf, 1);
StandardOut::singleton()->printf(MESSAGE_ERROR, "PROFILING CPUPROFILE: %s", buf);
monstartup("roblox.so");
}
else
{
StandardOut::singleton()->printf(MESSAGE_ERROR, "PROFILING cleanup");
moncleanup();
}
return true; // does exist
#else
return false; // does not exist
#endif // JNI_PROFILER
}
} // extern "C"
+31
View File
@@ -0,0 +1,31 @@
#include "JNIUtil.h"
#include <string>
#include <jni.h>
namespace RBX
{
namespace JNI
{
std::string cacheDirectory;
std::string assetFolderPath;
std::string jstringToStdString(JNIEnv *jenv, jstring jstrName)
{
jboolean isCopy;
const char *nativeString = jenv->GetStringUTFChars(jstrName, &isCopy);
const size_t kLength = strlen(nativeString)+1;
char *cstr = new char[kLength];
strncpy(cstr, nativeString, kLength);
if (isCopy)
{
jenv->ReleaseStringUTFChars(jstrName, nativeString);
}
std::string str = cstr;
delete [] cstr;
return str;
}
} // JNI
} // RBX
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <jni.h>
namespace RBX
{
namespace JNI
{
std::string jstringToStdString(JNIEnv *jenv, jstring jstrName);
} // namespace JNI
} // namespace RBX
+225
View File
@@ -0,0 +1,225 @@
#include "LogManager.h"
#include "RobloxUtilities.h"
#include "JNIMain.h"
#include <fstream>
#include <utility>
#include <boost/unordered_map.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/algorithm/string/replace.hpp>
#include "util/FileSystem.h"
#include "util/Guid.h"
#include "util/standardout.h"
#include "util/RobloxGoogleAnalytics.h"
#include "util/Http.h"
#include "util/Statistics.h"
#include "util/MemoryStats.h"
#include "v8datamodel/Stats.h"
#include "rbx/signal.h"
#include "FastLog.h"
#include "RbxAssert.h"
#include <android/log.h>
DYNAMIC_FASTINTVARIABLE(AndroidInfluxHundredthsPercentage, 0)
// Traditional Android macros.
#define LOG_TAG "roblox_jni"
#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOG_WARNING(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define CHANNEL_OUTPUT 1
using namespace RBX;
namespace RBX
{
namespace JNI
{
extern std::string exceptionReasonFilename; // set in JNIRobloxSettings.cpp
std::string platformUserAgent; // set in JNIRobloxSettings.cpp
std::string robloxVersion; // set in JNIRobloxSettings.cpp
extern int lastPlaceId; // set in JNIGLActivity.cpp
}
}
namespace
{
//static boost::shared_ptr<google_breakpad::ExceptionHandler> breakpadExceptionHandler;
static bool cleanupCrashDumps;
std::string getFastLogGuid()
{
std::string logGuid;
RBX::Guid::generateStandardGUID(logGuid);
logGuid = logGuid.substr(1, 6);
return logGuid;
}
static void ResponseFunc(std::string* response, std::exception* exception) { }
static void standardOutCallback(const StandardOutMessage &msg)
{
switch (msg.type)
{
case MESSAGE_OUTPUT:
case MESSAGE_INFO:
LOG_INFO("%s", msg.message.c_str());
break;
case MESSAGE_SENSITIVE:
case MESSAGE_WARNING:
LOG_WARNING("%s", msg.message.c_str());
break;
case MESSAGE_ERROR:
LOG_ERROR("%s", msg.message.c_str());
break;
default:
LOG_ERROR("Standard Message Out set with incorrect Message Type");
break;
}
}
static bool assertionHook(const char *expr, const char *filename, int lineNumber)
{
StandardOut::singleton()->printf(MESSAGE_ERROR, "%s (%s:%d)", expr, filename, lineNumber);
return true; // allow assertion handler to perform rawBreak.
}
class LogManager
{
typedef boost::unordered_map<FLog::Channel, boost::shared_ptr<std::ostream> > Channels;
boost::filesystem::path kLogPath;
std::string kLogGuid;
bool kInitialized;
Channels channels;
boost::mutex mutex;
rbx::signals::scoped_connection messageOutConnection;
public:
LogManager() : kInitialized(false)
{
messageOutConnection = StandardOut::singleton()->messageOut.connect(&standardOutCallback);
StandardOut::singleton()->printf(MESSAGE_INFO, "StandardOut ready.");
setAssertionHook(&assertionHook);
}
~LogManager()
{
boost::mutex::scoped_lock lock(mutex);
channels.clear(); // close all files and forget the file handles
messageOutConnection.disconnect();
LOG_INFO("StandardOut disconnected.");
}
void init(
const boost::filesystem::path &logPath,
const std::string &guid)
{
boost::mutex::scoped_lock lock(mutex);
kLogPath = logPath;
kLogGuid = guid;
kInitialized = true;
}
void writeEntry(const FLog::Channel &channelId, const char *message)
{
RBXASSERT(kInitialized);
boost::mutex::scoped_lock lock(mutex);
const Channels::iterator it = channels.find(channelId);
typename Channels::mapped_type stream;
if (channels.end() == it)
{
const int kLogChannel = channelId;
std::stringstream fileNameSS;
fileNameSS << "log_" << kLogGuid << "_" << kLogChannel << ".txt";
boost::filesystem::path path = kLogPath / fileNameSS.str();
StandardOut::singleton()->printf(MESSAGE_INFO, "Opening log file at %s", path.c_str());
typename Channels::value_type pair = std::make_pair(channelId, boost::shared_ptr<std::ostream>(new std::ofstream(path.c_str())));
if (!pair.second)
{
std::stringstream ss;
ss << "Could not open file: " << path.c_str();
throw RBX::runtime_error(ss.str().c_str());
}
else
{
channels.insert(pair);
stream = pair.second;
}
}
else
{
stream = it->second;
}
*stream << message << '\n';
}
}; // LogManager
static LogManager channels;
static void fastLogMessageCallback(FLog::Channel channelId, const char* message)
{
switch (channelId)
{
case CHANNEL_OUTPUT:
StandardOut::singleton()->printf(MESSAGE_INFO, "%s", message);
break;
default:
channels.writeEntry(channelId, message);
break;
}
}
} // namespace
namespace RBX
{
namespace JNI
{
namespace LogManager
{
void setupFastLog()
{
std::string logGuid = getFastLogGuid();
StandardOut::singleton()->printf(MESSAGE_INFO, "LogManager::kLogGuid = %s", logGuid.c_str());
std::string logPath = FileSystem::getCacheDirectory(true, "Log").string();
StandardOut::singleton()->printf(MESSAGE_INFO, "LogManager::kLogPath = %s", logPath.c_str());
channels.init(logPath, logGuid);
FLog::SetExternalLogFunc(&fastLogMessageCallback);
StandardOut::singleton()->printf(MESSAGE_INFO, "FastLog system ready.");
}
void tearDownFastLog()
{
FLog::SetExternalLogFunc(NULL);
StandardOut::singleton()->printf(MESSAGE_INFO, "FastLog system offline.");
}
void setupBreakpad(bool cleanup)
{
StandardOut::singleton()->printf(MESSAGE_INFO, "Initializing breakpad.");
}
} // namespace LogManager
} // namespace JNI
} // namespace RBX
+14
View File
@@ -0,0 +1,14 @@
#pragma once
namespace RBX
{
namespace JNI
{
namespace LogManager
{
void setupFastLog();
void tearDownFastLog();
void setupBreakpad(bool cleanup);
} // namespace LogManager
} // namespace JNI
} // namespace RBX
+616
View File
@@ -0,0 +1,616 @@
//
// PlaceLauncher.m
// RobloxMobile
//
// Created by David York on 9/25/12.
// Copyright (c) 2012 ROBLOX. All rights reserved.
//
#include "PlaceLauncher.h"
#include "util/Statistics.h"
#include "RobloxView.h"
#include "RobloxInfo.h"
#include "JNIMain.h"
#include "RobloxUtilities.h"
#include <iostream> // std::cout
#include <fstream> // std::ifstream
#include "v8datamodel/DebugSettings.h"
#include "v8datamodel/Workspace.h"
#include "v8datamodel/GuiService.h"
#include "v8datamodel/TeleportService.h"
#include "v8datamodel/LoginService.h"
#include "V8DataModel/ContentProvider.h"
#include "v8datamodel/MeshContentProvider.h"
#include "v8datamodel/TextureContentProvider.h"
#include "v8datamodel/FastLogSettings.h"
#include "V8DataModel/UserInputService.h"
#include "script/ScriptContext.h"
#include "util/Statistics.h"
#include "util/standardout.h"
#include "util/MemoryStats.h"
#include "v8datamodel/Stats.h"
#include "util/RobloxGoogleAnalytics.h"
#include "util/Http.h"
#include "LogManager.h"
#include "rbx/Profiler.h"
#include <rapidjson/document.h>
DYNAMIC_LOGVARIABLE(PlaceLauncher, 0)
DYNAMIC_LOGGROUP(NetworkTrace)
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
DYNAMIC_FASTINT(AndroidInfluxHundredthsPercentage)
using namespace RBX;
using namespace RBX::JNI;
namespace
{
static const std::string kAndroidClientAppSettings = "AndroidAppSettings";
static const std::string kAndroidClientSettingsAPIKey = "D6925E56-BFB9-4908-AAA2-A5B1EC4B2D79";
static const std::string kStartGameURL = "%sGame/PlaceLauncher.ashx?request=%s&%s&isPartyLeader=false&gender=&isTeleport=false";
static const std::string kStartGameStatusURL = "%sGame/PlaceLauncher.ashx?request=CheckGameJobStatus&jobId=%s";
} // namespace
namespace RBX
{
namespace JNI
{
extern std::string platformUserAgent; // Comes from LogManager
}
}
PlaceLauncher::PlaceLauncher()
{
teleporter.reset(new Teleporter(RBX::FunctionMarshaller::GetWindow()));
RBX::TeleportService::SetCallback(teleporter.get());
};
PlaceLauncher& PlaceLauncher::getPlaceLauncher()
{
static PlaceLauncher placeLauncher;
return placeLauncher;
}
static std::string ReadStringValue(std::string &data, std::string name)
{
std::string result;
int pos = data.find(name);
if (pos != -1)
{
int start = pos + name.length() + 3;
int end = data.find(",", start);
result = data.substr(start, end - start - 1);
}
pos = result.find("\\/"); // find first space
while (pos != std::string::npos)
{
result.replace(pos, 2, "/");
pos = result.find("\\/", pos + 1);
}
return result;
}
static RBX::ProtectedString fetchAndValidateScript(RBX::DataModel* dataModel, const std::string& urlScript)
{
RBX::Security::Impersonator impersonate(RBX::Security::COM);
std::ostringstream data;
if (RBX::ContentProvider::isUrl(urlScript))
{
RBX::DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write);
std::auto_ptr<std::istream> stream(RBX::ServiceProvider::create<RBX::ContentProvider>(dataModel)->getContent(RBX::ContentId(urlScript.c_str())));
boost::iostreams::copy(*stream, data);
}
else
return RBX::ProtectedString();
RBX::ProtectedString verifiedSource;
try
{
verifiedSource = RBX::ProtectedString::fromTrustedSource(data.str());
RBX::ContentProvider::verifyScriptSignature(verifiedSource, true);
}
catch(std::bad_alloc& e)
{
throw;
}
catch(RBX::base_exception& e)
{
return RBX::ProtectedString();
}
return verifiedSource;
}
static void executeUrlJoinScript(boost::shared_ptr<RBX::Game> game, const std::string& urlScript)
{
RBXASSERT(urlScript.find("join.ashx"));
boost::shared_ptr<RBX::DataModel> dataModel = game->getDataModel();
RBX::ProtectedString verifiedSource = fetchAndValidateScript(dataModel.get(), urlScript);
if (verifiedSource.empty())
return;
RBX::DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write);
if (dataModel->isClosed())
return;
// new join script
std::string dataString = verifiedSource.getSource();
int firstNewLineIndex = dataString.find("\r\n");
if (dataString[firstNewLineIndex+2] == '{')
{
RobloxUtilities::getRobloxUtilities().getGameTimer().reset();
game->configurePlayer(RBX::Security::COM, dataString.substr(firstNewLineIndex+2));
return;
}
// old join script
RBX::ScriptContext* context = dataModel->create<RBX::ScriptContext>();
context->executeInNewThread(RBX::Security::COM, verifiedSource, "Start Script");
}
static void joinGamePlaceId(StartGameParams sgp, shared_ptr<RBX::Game> game)
{
RobloxUtilities::getRobloxUtilities().getGameTimer().reset();
try
{
int retrys = 5;
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO,"PlaceLauncher::joinGamePlaceId placeId = %i, userId = %d, accessCode = %s, gameId = %s, joinRequestType: %d\n", sgp.placeId, sgp.userId, sgp.accessCode.c_str(), sgp.gameId.c_str(), sgp.joinRequestType);
const char* base = GetBaseURL().c_str();
std::string requestParamsString, requestType;
std::string formattedParams;
switch (sgp.joinRequestType)
{
case JOIN_GAME_REQUEST_PLACEID:
requestParamsString = "placeId=%d";
formattedParams = RBX::format(requestParamsString.c_str(), sgp.placeId);
requestType = "RequestGame";
break;
case JOIN_GAME_REQUEST_USERID:
requestParamsString = "userId=%d";
formattedParams = RBX::format(requestParamsString.c_str(), sgp.userId);
requestType = "RequestFollowUser";
break;
case JOIN_GAME_REQUEST_PRIVATE_SERVER:
requestParamsString = "placeId=%d&accessCode=%s";
formattedParams = RBX::format(requestParamsString.c_str(), sgp.placeId, sgp.accessCode.c_str());
requestType = "RequestPrivateGame";
break;
case JOIN_GAME_REQUEST_GAME_INSTANCE:
requestParamsString = "placeId=%d&gameId=%s";
formattedParams = RBX::format(requestParamsString.c_str(), sgp.placeId, sgp.gameId.c_str());
requestType = "RequestGameJob";
break;
}
std::string response;
bool found = false;
int status = -1;
std::string url = RBX::format(kStartGameURL.c_str(), base, requestType.c_str(), formattedParams.c_str());
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO,"PlaceLauncher URL : %s\n", url.c_str());
std::string jobId;
while (retrys >= 0)
{
bool retryUsed = true;
response = "";
RBX::Http(url).get(response);
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO,"PlaceLauncher Response : %s\n", response.c_str());
rapidjson::Document doc;
doc.Parse<rapidjson::kParseDefaultFlags>(response.c_str());
RBXASSERT(doc.HasMember("status"));
status = doc["status"].GetInt();
// Place join status results
// Waiting = 0,
// Loading = 1,
// Joining = 2,
// Disabled = 3,
// Error = 4,
// GameEnded = 5,
// GameFull = 6
// UserLeft = 10
// Restricted = 11
if (2 == status)
{
// not most efficient way to check status,
// but we have web calls so strstr is not a big deal
found = true;
break;
}
else if (0 == status || 1 == status)
{
// 0 or 1 is not an error - it is a sign that we should wait
retryUsed = false;
RBXASSERT(doc.HasMember("jobId"));
jobId = doc["jobId"].GetString();
// Check game status no matter what type of join we're attempting
url = RBX::format(kStartGameStatusURL.c_str(), base, Http::urlEncode(jobId).c_str());
}
else
break;
int sleepTime = 250*1000;
if (retryUsed)
{
--retrys;
sleepTime = 999*1000;
}
::usleep(sleepTime);
}
if (found)
{
executeUrlJoinScript(game, ReadStringValue(response, "joinScriptUrl"));
}
else
{
if (sgp.joinRequestType == JOIN_GAME_REQUEST_PLACEID)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "PlaceLauncher: Cannot connect to place %d, return from GetPlaceLauncher.ashx = %s", sgp.placeId, response.c_str());
else if (sgp.joinRequestType == JOIN_GAME_REQUEST_USERID)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "PlaceLauncher: Cannot follow user %d, return from GetPlaceLauncher.ashx = %s", sgp.userId, response.c_str());
else if (sgp.joinRequestType == JOIN_GAME_REQUEST_PRIVATE_SERVER)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "PlaceLauncher: Cannot join private server, accessCode = %s, return from GetPlaceLauncher.ashx = %s", sgp.accessCode.c_str(), response.c_str());
else if (sgp.joinRequestType)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "PlaceLauncher: Cannot join game instance, gameId = %s, return from GetPlaceLauncher.ashx = %s", sgp.gameId.c_str(), response.c_str());
// Place join status results
// Waiting = 0,
// Loading = 1,
// Joining = 2,
// Disabled = 3,
// Error = 4,
// GameEnded = 5,
// GameFull = 6
// UserLeft = 10
// Restricted = 11
PlaceLauncher::handleStartGameFailure(status);
}
}
catch (const RBX::base_exception& e)
{
if (sgp.joinRequestType == JOIN_GAME_REQUEST_PLACEID)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR,"PlaceLauncher: Exception thrown: Can't join place %d, because %s\n", sgp.placeId, e.what());
else if (sgp.joinRequestType == JOIN_GAME_REQUEST_USERID)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR,"PlaceLauncher: Exception thrown: Can't follow user %d, because %s\n", sgp.userId, e.what());
else if (sgp.joinRequestType == JOIN_GAME_REQUEST_PRIVATE_SERVER)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "PlaceLauncher: Exception thrown: Cannot join private server, accessCode = %s, because %s", sgp.accessCode.c_str(), e.what());
else if (sgp.joinRequestType)
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "PlaceLauncher: Exception thrown: Cannot join game instance, gameId = %s, because %s", sgp.gameId.c_str(), e.what());
//[[PlaceLauncher sharedInstance] handleStartGameFailure];
}
}
void PlaceLauncher::handleStartGameFailure(int status = 99)
{
// Place join status results
// 0, 1, or 2 is handled in joinGamePlaceId above
// Disabled = 3,
// Error = 4,
// GameEnded = 5,
// GameFull = 6
// UserLeft = 10
// Restricted = 11
std::string errorMsgName; // this is the name of the error message string in res/values/strings.xml
std::string fastLogMsg; // only used for logging; not displayed to the user
switch (status)
{
case 3:
errorMsgName = "GameStartFailureDisabled";
fastLogMsg = "PlaceLauncher failure - game was disabled.";
break;
case 4:
errorMsgName = "GameStartFailureError";
fastLogMsg = "PlaceLauncher failure - game failed to start.";
break;
case 5:
errorMsgName = "GameStartFailureGameEnded";
fastLogMsg = "PlaceLauncher failure - game has ended.";
break;
case 6:
errorMsgName = "GameStartFailureGameFull";
fastLogMsg = "PlaceLauncher failure - game is full.";
break;
case 10:
errorMsgName = "GameStartFailureUserLeft";
fastLogMsg = "PlaceLauncher failure - the user you were following has left the game.";
break;
case 11:
errorMsgName = "GameStartFailureRestricted";
fastLogMsg = "PlaceLauncher failure - game not available for this platform.";
break;
case 99:
default:
errorMsgName = "GameStartFailureUnknown";
fastLogMsg = "PlaceLauncher failure - unknown game start failure.";
break;
}
RBX::StandardOut::singleton()->printf(MESSAGE_INFO, "in handleGameStartFailure, msg is %s", errorMsgName.c_str());
FASTLOG1(DFLog::PlaceLauncher, "%s", fastLogMsg.c_str());
JNI::exitGameWithError(errorMsgName); // from JNIMain
}
bool PlaceLauncher::prepareGame( const StartGameParams& sgp )
{
StandardOut::singleton()->printf(MESSAGE_INFO, "Asset Path in Place Launch: %s", RobloxInfo::getRobloxInfo().getAssetFolderPath().c_str());
FASTLOG(DFLog::PlaceLauncher, "PlaceLauncher prepareGame - START");
RBX::ContentProvider::setAssetFolder(RobloxInfo::getRobloxInfo().getAssetFolderPath().c_str());
Http::rbxUserAgent = platformUserAgent;
RBX::Game::globalInit(false);
RBX::TeleportService::SetBaseUrl(GetBaseURL().c_str());
/*
if(needsOnline)
{
Reachability* reachability = [Reachability reachabilityForInternetConnection];
NetworkStatus remoteStatus = [reachability currentReachabilityStatus];
if(remoteStatus == NotReachable)
{
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR,"PlaceLauncher: No Network Connection available");
[RobloxAlert RobloxAlertWithMessage:NSLocalizedString(@"ConnectionError", nil)];
return false;
}
else if(remoteStatus == ReachableViaWWAN)
{
// if user preference is for wifi only, don't allow this
NSUserDefaults *userDefaults =[NSUserDefaults standardUserDefaults];
if([[userDefaults stringForKey:@"wifionly_preference"] boolValue])
{
[RobloxAlert RobloxAlertWithMessage:NSLocalizedString(@"WiFiOnlyError", nil)];
return false;
}
}
}*/
std::string ios = "i";
ios += "o";
ios += "s";
ios = ios + "," + ios; // slight obfuscation
RBX::DataModel::hash = ios;
{
RBX::Security::Impersonator impersonate(RBX::Security::RobloxGameScript_);
RBX::GlobalBasicSettings::singleton()->loadState("");
}
RBX::Profiler::onThreadCreate("Main");
RBX::TaskScheduler::singleton().setThreadCount(RBX::TaskSchedulerSettings::singleton().getThreadPoolConfig());
FASTLOG(DFLog::PlaceLauncher, "PlaceLauncher prepareGame - END");
return true;
}
void initClientSettings()
{
// This needs to be put in a manager and cached
std::string clientSettingsData;
std::string androidAppSettingsData;
FetchClientSettingsData(CLIENT_APP_SETTINGS_STRING, CLIENT_SETTINGS_API_KEY, &clientSettingsData);
FetchClientSettingsData(kAndroidClientAppSettings.c_str(), kAndroidClientSettingsAPIKey.c_str(), &androidAppSettingsData);
RBX::ClientAppSettings settings = RBX::ClientAppSettings::singleton();
LoadClientSettingsFromString(CLIENT_APP_SETTINGS_STRING, clientSettingsData, &RBX::ClientAppSettings::singleton());
LoadClientSettingsFromString(kAndroidClientAppSettings.c_str(), androidAppSettingsData, &RBX::ClientAppSettings::singleton());
// Reset synchronized flags, they should be set by the server
FLog::ResetSynchronizedVariablesState();
}
static void initControlView(RobloxView* rbxView, bool isTouchDevice)
{
if(DataModel* dm = rbxView->getDataModel().get())
{
if(RBX::UserInputService* inputService = RBX::ServiceProvider::create<RBX::UserInputService>(dm))
{
inputService->setTouchEnabled(isTouchDevice);
}
}
}
void PlaceLauncher::finishGameSetup( boost::shared_ptr<RBX::Game> game)
{
rbxView = RobloxView::create_view(currentGame, gameParams.view, gameParams.viewWidth, gameParams.viewHeight);
rbxView->getDataModel()->submitTask(boost::bind(initControlView, rbxView, gameParams.isTouchDevice), RBX::DataModelJob::Write);
}
shared_ptr<RBX::Game> PlaceLauncher::setupGame( const StartGameParams& sgp )
{
FASTLOG(DFLog::PlaceLauncher, "PlaceLauncher setUpGame");
if(isCurrentlyPlayingGame)
return shared_ptr<RBX::Game>();
isCurrentlyPlayingGame = true;
initClientSettings();
RobloxGoogleAnalytics::lotteryInit(
ClientAppSettings::singleton().GetValueGoogleAnalyticsAccountPropertyIDPlayer(),
ClientAppSettings::singleton().GetValueGoogleAnalyticsThreadPoolMaxScheduleSize(),
ClientAppSettings::singleton().GetValueGoogleAnalyticsLoadPlayer());
StandardOut::singleton()->printf(MESSAGE_INFO, "account = %s, schedule = %d",
ClientAppSettings::singleton().GetValueGoogleAnalyticsAccountPropertyIDPlayer(),
ClientAppSettings::singleton().GetValueGoogleAnalyticsThreadPoolMaxScheduleSize());
RBX::Http::SetUseStatistics(true);
if(prepareGame( sgp ))
{
FASTLOGS(DFLog::PlaceLauncher, "Game's base URL: %s", GetBaseURL().c_str());
currentGame = shared_ptr<RBX::Game>( new RBX::SecurePlayerGame(NULL, GetBaseURL().c_str()) );
rbxView = RobloxView::create_view(currentGame, sgp.view, sgp.viewWidth, sgp.viewHeight);
rbxView->getDataModel()->submitTask(boost::bind(initControlView, rbxView, gameParams.isTouchDevice), RBX::DataModelJob::Write);
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
currentGame->getDataModel()->create<RBX::ScriptContext>();
return currentGame;
}
return shared_ptr<RBX::Game>();
}
shared_ptr<RBX::Game> PlaceLauncher::setupPreloadedGame( const StartGameParams& sgp )
{
FASTLOG(DFLog::PlaceLauncher, "PlaceLauncher SetupPreloadedGame");
return setupGame( sgp );
}
bool PlaceLauncher::startGame( boost::function0 <void> scriptFunction , shared_ptr<RBX::Game> preloadedGame)
{
// Start the script thread!
boost::thread(RBX::thread_wrapper(scriptFunction, "GameStartScript"));
return true;
}
bool PlaceLauncher::startGame( const StartGameParams& sgp )
{
gameParams = sgp;
FASTLOG(DFLog::PlaceLauncher, "PlaceLauncher StartGame");
shared_ptr<RBX::Game> game = setupPreloadedGame( sgp );
if( game != shared_ptr<RBX::Game>())
{
currentGame = game;
return startGame(boost::bind(&joinGamePlaceId, sgp, game), game);
}
return true;
}
void PlaceLauncher::leaveGame ( bool userRequestedLeave)
{
RBX::Time::Interval gamePlayInterval = RobloxUtilities::getRobloxUtilities().getGameTimer().reset();
RBX::Analytics::InfluxDb::Points points;
points.addPoint("SessionReport" , "AndroidSuccess");
points.addPoint("FreeMemoryKB" , RBX::MemoryStats::freeMemoryBytes());
points.addPoint("UsedMemoryKB" , RBX::MemoryStats::usedMemoryBytes());
points.addPoint("PlayTime" , gamePlayInterval.seconds());
points.report("Android-RobloxPlayer-SessionReport", DFInt::AndroidInfluxHundredthsPercentage);
deleteRobloxView(true);
}
void PlaceLauncher::deleteRobloxView( bool resetCurrentGame )
{
if(resetCurrentGame)
{
// remove all shared ptr to games so it can properly be destroyed
currentGame.reset();
}
if( teleporter )
{
teleporter.reset();
}
// do actual destruction of the Roblox View, only if it isn't NULL
if (rbxView)
{
// Destroy the view synchronously
// Take care to zero-out view pointer beforehand to support reentrancy
RobloxView* view = rbxView;
rbxView = NULL;
delete view;
}
}
static void joinGameTeleport(std::string url, std::string ticket, std::string script, shared_ptr<RBX::Game> game)
{
try
{
// get authentication URL
std::string compound = url;
if (!ticket.empty())
{
compound += "?suggest=";
compound += ticket;
}
// issue an authentication request
std::string result;
try
{
RBX::Http http(compound.c_str());
http.setAuthDomain(GetBaseURL().c_str());
http.get(result);
}
catch (const RBX::base_exception& e)
{
}
// run game
executeUrlJoinScript(game, script);
}
catch (const RBX::base_exception& e)
{
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR,"PlaceLauncher: Teleport failed: %s\n", e.what());
}
}
void PlaceLauncher::teleport( std::string ticket, std::string authUrl, std::string script)
{
if (!rbxView) return;
// first do an empty reset so serviceprovider oldProvider is called first
currentGame.reset();
currentGame.reset(new RBX::SecurePlayerGame(NULL, GetBaseURL().c_str()));
rbxView->replaceGame(currentGame);
rbxView->getDataModel()->submitTask(boost::bind(initControlView, rbxView, gameParams.isTouchDevice), RBX::DataModelJob::Write);
// Don't use submit task on data model for this thread, the script fetched in
// the spawned thread does data model setup.
// joinGameTeleport will take ownership of the game.
boost::thread joinScriptThread(boost::bind(&joinGameTeleport, authUrl, ticket, script, currentGame));
}
void PlaceLauncher::shutDownGraphics()
{
if (!rbxView) return;
rbxView->shutDownGraphics(currentGame);
}
void PlaceLauncher::startUpGraphics(void *wnd, unsigned int width, unsigned int height)
{
if (!rbxView) return;
rbxView->startUpGraphics(currentGame, wnd, width, height);
}
+85
View File
@@ -0,0 +1,85 @@
// PlaceLauncher.h
#pragma once
#include <string>
#include <boost/iostreams/copy.hpp>
#include "LogManager.h"
#include "Teleporter.h"
#include "v8datamodel/Game.h"
#include "v8tree/Instance.h"
#include "util/standardout.h"
#include "rbx/signal.h"
enum JoinGameRequest {
JOIN_GAME_REQUEST_PLACEID,
JOIN_GAME_REQUEST_USERID,
JOIN_GAME_REQUEST_PRIVATE_SERVER,
JOIN_GAME_REQUEST_GAME_INSTANCE
};
class Teleporter;
class RobloxView;
struct StartGameParams
{
int viewWidth;
int viewHeight;
void* view;
int placeId;
int userId;
std::string accessCode;
std::string gameId;
std::string assetFolderPath;
bool isTouchDevice;
JoinGameRequest joinRequestType;
};
class PlaceLauncher
{
RobloxView* rbxView;
boost::scoped_ptr<Teleporter> teleporter;
bool isCurrentlyPlayingGame;
bool isLeavingGame;
int lastPlaceId;
StartGameParams gameParams;
// player join tracking
rbx::signals::connection childConnection;
rbx::signals::connection playerConnection;
shared_ptr<RBX::Game> currentGame;
PlaceLauncher();
PlaceLauncher(PlaceLauncher const&);
void operator=(PlaceLauncher const&);
shared_ptr<RBX::Game> setupGame( const StartGameParams& sgp );
shared_ptr<RBX::Game> setupPreloadedGame( const StartGameParams& sgp );
bool startGame( boost::function0 <void> scriptFunction, shared_ptr<RBX::Game> preloadedGame);
bool prepareGame(const StartGameParams& sgp);
void deleteRobloxView( bool resetCurrentGame );
void finishGameSetup( boost::shared_ptr<RBX::Game> game);
public:
static PlaceLauncher& getPlaceLauncher();
RobloxView* getRbxView() { return rbxView; }
bool startGame( const StartGameParams& sgp );
void leaveGame ( bool userRequestedLeave);
void teleport( std::string ticket, std::string authUrl, std::string script);
static void handleStartGameFailure(int status);
weak_ptr<RBX::Game> getCurrentGame() { return currentGame; }
void shutDownGraphics();
void startUpGraphics(void *wnd, unsigned int width, unsigned int height);
};
+2
View File
@@ -0,0 +1,2 @@
#include "RobloxInfo.h"
+39
View File
@@ -0,0 +1,39 @@
#ifndef ROBLOXINFO_H
#define ROBLOXINFO_H
#include <iostream>
#include "rbx/Debug.h"
class RobloxInfo
{
public:
static RobloxInfo& getRobloxInfo()
{
static RobloxInfo robloxInfo;
return robloxInfo;
}
std::string getAssetFolderPath()
{
if(assetFolderPath.empty())
RBXASSERT("Asset Path is not Set");
return assetFolderPath;
}
void setAssetFolderPath(std::string assetPath)
{
assetFolderPath = assetPath;
}
private:
RobloxInfo() {};
RobloxInfo(RobloxInfo const&);
void operator=(RobloxInfo const&);
std::string assetFolderPath;
};
#endif // ROBLOXINFO_H
+689
View File
@@ -0,0 +1,689 @@
#include "RobloxInput.h"
#include "PlaceLauncher.h"
#include "V8DataModel/Workspace.h"
#include "V8DataModel/GamepadService.h"
#include "V8DataModel/UserInputService.h"
#include "V8DataModel/GameBasicSettings.h"
#include "V8datamodel/TouchInputService.h"
#include "Network/Players.h"
#define GRAVITY_ACCELERATION 9.80665f
// how quickly (in seconds) a user has to tap the screen to have a mouse down/up gesture sent
static float tapSensitivity = 0.25f;
// how much a tap can move in pixels on screen
static int tapTouchMoveTolerance = 20;
RobloxInput::RobloxInput()
{
if (RBX::UserInputService* inputService = getInputService())
{
// code below will listen to UserInputService when it fires a mouse event post event (bool tells whether the mouse event was used by app)
inputService->processedEventSignal.connect(boost::bind(&RobloxInput::postEventProcessed, this, _1, _2));
inputService->updateInputSignal.connect(boost::bind(&RobloxInput::processControllerBufferMap, this));
inputService->getSupportedGamepadKeyCodesSignal.connect(boost::bind(&RobloxInput::getSupportedGamepadKeyCodes, this, _1));
}
// init ptrs to the services
getTouchService();
getGamepadService();
tapLocation = RBX::Vector3::zero();
connectedControllerMap[RBX::InputObject::TYPE_GAMEPAD1] = false;
connectedControllerMap[RBX::InputObject::TYPE_GAMEPAD2] = false;
connectedControllerMap[RBX::InputObject::TYPE_GAMEPAD3] = false;
connectedControllerMap[RBX::InputObject::TYPE_GAMEPAD4] = false;
}
RBX::DataModel* RobloxInput::getDataModel()
{
weak_ptr<RBX::Game> game = PlaceLauncher::getPlaceLauncher().getCurrentGame();
if (shared_ptr<RBX::Game> sharedGame = game.lock())
{
if (RBX::DataModel* dm = sharedGame->getDataModel().get())
{
return dm;
}
}
return NULL;
}
void RobloxInput::sendWorkspaceEvent(RBX::Vector3 touchPosition)
{
if (RBX::UserInputService* inputService = getInputService())
{
shared_ptr<RBX::InputObject> fakeMouseDownEvent = RBX::Creatable<
RBX::Instance>::create<RBX::InputObject>(
RBX::InputObject::TYPE_MOUSEBUTTON1,
RBX::InputObject::INPUT_STATE_BEGIN, touchPosition,
G3D::Vector3( 0.0f, 0.0f, 0.0f ),
getDataModel());
inputService->processToolEvent(fakeMouseDownEvent);
shared_ptr<RBX::InputObject> fakeMouseUpEvent = RBX::Creatable<
RBX::Instance>::create<RBX::InputObject>(
RBX::InputObject::TYPE_MOUSEBUTTON1,
RBX::InputObject::INPUT_STATE_END, touchPosition,
G3D::Vector3( 0.0f, 0.0f, 0.0f ),
getDataModel());
inputService->processToolEvent(fakeMouseUpEvent);
}
}
void RobloxInput::sendWorkspaceEvent(shared_ptr<RBX::InputObject> inputObject)
{
sendWorkspaceEvent(inputObject->getRawPosition());
}
RBX::GamepadService* RobloxInput::getGamepadService()
{
if (shared_ptr<RBX::GamepadService> gamepadService = weakGamepadService.lock())
{
return gamepadService.get();
}
else
{
if (RBX::DataModel* dm = getDataModel())
{
dm->submitTask([=](...)
{
if (RBX::GamepadService* gamepadService = RBX::ServiceProvider::create<RBX::GamepadService>(dm))
{
weakGamepadService = weak_from(gamepadService);
}
}, RBX::DataModelJob::Write);
}
}
return NULL;
}
RBX::UserInputService* RobloxInput::getInputService()
{
if (RBX::DataModel* dm = getDataModel())
{
if (RBX::UserInputService* inputService = RBX::ServiceProvider::find<
RBX::UserInputService>(dm))
{
return inputService;
}
}
return NULL;
}
RBX::TouchInputService* RobloxInput::getTouchService()
{
if (shared_ptr<RBX::TouchInputService> touchService = weakTouchInputService.lock())
{
return touchService.get();
}
else
{
if (RBX::DataModel* dm = getDataModel())
{
dm->submitTask([=](...)
{
if (RBX::TouchInputService* touchService = RBX::ServiceProvider::create<RBX::TouchInputService>(dm))
{
weakTouchInputService = weak_from(touchService);
}
}, RBX::DataModelJob::Write);
}
}
return NULL;
}
void RobloxInput::postEventProcessed(const shared_ptr<RBX::Instance>& event, bool processedEvent)
{
if (processedEvent)
{
if (RBX::InputObject* inputObject = RBX::Instance::fastDynamicCast<RBX::InputObject>(event.get()))
{
if (inputObject->isTouchEvent() && inputObject->getRawPosition() == tapLocation)
{
tapEventId = -1;
tapLocation = RBX::Vector3::zero();
}
}
}
}
void RobloxInput::passTextInput(RBX::TextBox* textBox, std::string newText, bool enterPressed, int cursorPosition)
{
if (enterPressed)
{
if (RBX::UserInputService* userInputService = getInputService())
{
if (!userInputService->showStatsBasedOnInputString(newText.c_str()))
{
userInputService->textboxDidFinishEditing(newText.c_str(), true);
}
}
}
else
{
if (textBox)
{
textBox->setBufferedText(newText, cursorPosition);
}
}
}
void RobloxInput::externalReleaseFocus(RBX::TextBox* currentTextBox)
{
if (currentTextBox)
currentTextBox->externalReleaseFocus(currentTextBox->getText().c_str(), false, shared_ptr<RBX::InputObject>());
}
void RobloxInput::sendGestureEvent(RBX::UserInputService::Gesture gestureToSend,
shared_ptr<RBX::Reflection::Tuple> args,
shared_ptr<RBX::Reflection::ValueArray> touchLocations)
{
if (RBX::UserInputService* userInputService = getInputService())
userInputService->addGestureEventToProcess(gestureToSend, touchLocations, args);
}
RBX::KeyCode getKeyCodeFromAndroidAxisInt(int axisKeyCode, int value = -1)
{
switch(axisKeyCode)
{
case 22:
case 18:
{
return RBX::SDLK_GAMEPAD_BUTTONR2;
}
case 23:
case 17:
{
return RBX::SDLK_GAMEPAD_BUTTONL2;
}
case 0:
case 1:
{
return RBX::SDLK_GAMEPAD_THUMBSTICK1;
}
case 11:
case 14:
{
return RBX::SDLK_GAMEPAD_THUMBSTICK2;
}
case 15:
{
if (value < 0)
return RBX::SDLK_GAMEPAD_DPADLEFT;
else
return RBX::SDLK_GAMEPAD_DPADRIGHT;
}
case 16:
{
if (value < 0)
return RBX::SDLK_GAMEPAD_DPADDOWN;
else
return RBX::SDLK_GAMEPAD_DPADUP;
}
default:
{
return RBX::SDLK_UNKNOWN;
}
}
}
RBX::KeyCode getKeyCodeFromAndroidButtonInt(int androidKeycode)
{
switch (androidKeycode)
{
case 96: return RBX::SDLK_GAMEPAD_BUTTONA;
case 97: return RBX::SDLK_GAMEPAD_BUTTONB;
case 102: return RBX::SDLK_GAMEPAD_BUTTONL1;
case 104: return RBX::SDLK_GAMEPAD_BUTTONL2;
case 103: return RBX::SDLK_GAMEPAD_BUTTONR1;
case 105: return RBX::SDLK_GAMEPAD_BUTTONR2;
case 109: return RBX::SDLK_GAMEPAD_BUTTONSELECT;
case 108: return RBX::SDLK_GAMEPAD_BUTTONSTART;
case 106: return RBX::SDLK_GAMEPAD_BUTTONL3;
case 107: return RBX::SDLK_GAMEPAD_BUTTONR3;
case 99: return RBX::SDLK_GAMEPAD_BUTTONX;
case 100: return RBX::SDLK_GAMEPAD_BUTTONY;
case 20: return RBX::SDLK_GAMEPAD_DPADDOWN;
case 21: return RBX::SDLK_GAMEPAD_DPADLEFT;
case 22: return RBX::SDLK_GAMEPAD_DPADRIGHT;
case 19: return RBX::SDLK_GAMEPAD_DPADUP;
default: return RBX::SDLK_UNKNOWN;
}
}
void RobloxInput::handleGamepadButtonInput(int deviceId, int keyCode, int buttonState)
{
if (deviceIdToGamepadId.find(deviceId) == deviceIdToGamepadId.end())
{
return;
}
const RBX::InputObject::UserInputType gamepadEnum = deviceIdToGamepadId[deviceId];
const RBX::KeyCode rbxKeycode = getKeyCodeFromAndroidButtonInt(keyCode);
{
boost::mutex::scoped_lock mutex(ControllerBufferMutex);
RBX::Vector3 newValue(0,0,buttonState);
if (controllerBufferMap[gamepadEnum][rbxKeycode].empty() ||
controllerBufferMap[gamepadEnum][rbxKeycode].back() != newValue)
{
controllerBufferMap[gamepadEnum][rbxKeycode].push_back(newValue);
}
}
}
void RobloxInput::handleGamepadAxisInput(int deviceId, int actionType, float newValueX, float newValueY, float newValueZ)
{
if (deviceIdToGamepadId.find(deviceId) == deviceIdToGamepadId.end())
{
return;
}
if (RBX::GamepadService* gamepadService = getGamepadService())
{
const RBX::KeyCode rbxKeycode = getKeyCodeFromAndroidAxisInt(actionType, newValueZ);
const RBX::InputObject::UserInputType gamepadEnum = deviceIdToGamepadId[deviceId];
if (rbxKeycode == RBX::SDLK_GAMEPAD_DPADUP ||
rbxKeycode == RBX::SDLK_GAMEPAD_DPADDOWN ||
rbxKeycode == RBX::SDLK_GAMEPAD_DPADRIGHT ||
rbxKeycode == RBX::SDLK_GAMEPAD_DPADLEFT)
{
newValueZ = fabs(newValueZ);
}
if (rbxKeycode == RBX::SDLK_GAMEPAD_BUTTONR2 || rbxKeycode == RBX::SDLK_GAMEPAD_BUTTONL2 ||
rbxKeycode == RBX::SDLK_GAMEPAD_DPADUP ||
rbxKeycode == RBX::SDLK_GAMEPAD_DPADDOWN ||
rbxKeycode == RBX::SDLK_GAMEPAD_DPADRIGHT ||
rbxKeycode == RBX::SDLK_GAMEPAD_DPADLEFT)
{
{
boost::mutex::scoped_lock mutex(ControllerBufferMutex);
RBX::Vector3 newValue(0,0,newValueZ);
if (controllerBufferMap[gamepadEnum][rbxKeycode].empty() ||
controllerBufferMap[gamepadEnum][rbxKeycode].back() != newValue)
{
controllerBufferMap[gamepadEnum][rbxKeycode].push_back(newValue);
// since android combines up/down, left/right dpad as axis, but we
// use these as button values, make sure both dpad directions get their
// state returned to netural when we get a zero value
if (newValue.z == 0.0f)
{
if (rbxKeycode == RBX::SDLK_GAMEPAD_DPADUP)
{
controllerBufferMap[gamepadEnum][RBX::SDLK_GAMEPAD_DPADDOWN].push_back(newValue);
}
else if (rbxKeycode == RBX::SDLK_GAMEPAD_DPADRIGHT)
{
controllerBufferMap[gamepadEnum][RBX::SDLK_GAMEPAD_DPADLEFT].push_back(newValue);
}
}
}
}
}
else
{
{
boost::mutex::scoped_lock mutex(ControllerBufferMutex);
RBX::Vector3 newVector(newValueX,newValueY,0);
if (controllerBufferMap[gamepadEnum][rbxKeycode].empty() ||
controllerBufferMap[gamepadEnum][rbxKeycode].back() != newVector)
{
controllerBufferMap[gamepadEnum][rbxKeycode].push_back(newVector);
}
}
}
}
}
RBX::InputObject::UserInputType RobloxInput::mapDeviceIdToControllerEnum(int deviceId)
{
RBX::InputObject::UserInputType controllerNum = RBX::InputObject::TYPE_NONE;
if (deviceIdToGamepadId.find(deviceId) != deviceIdToGamepadId.end())
{
controllerNum = deviceIdToGamepadId[deviceId];
connectedControllerMap[controllerNum] = true;
return controllerNum;
}
for (ConnectedControllerMap::iterator iter = connectedControllerMap.begin(); iter != connectedControllerMap.end(); iter++)
{
if ((*iter).second == false)
{
controllerNum = (*iter).first;
break;
}
}
connectedControllerMap[controllerNum] = true;
deviceIdToGamepadId[deviceId] = controllerNum;
return controllerNum;
}
void RobloxInput::handleGamepadDisconnect(int deviceId)
{
if (deviceIdToGamepadId.find(deviceId) != deviceIdToGamepadId.end())
{
if (RBX::UserInputService* userInputService = getInputService())
{
RBX::InputObject::UserInputType controllerNum = deviceIdToGamepadId[deviceId];
userInputService->safeFireGamepadDisconnected(controllerNum);
connectedControllerMap[controllerNum] = false;
deviceIdToGamepadId.erase(deviceId);
}
}
}
void RobloxInput::handleGamepadConnect(int deviceId)
{
RBX::InputObject::UserInputType controllerNum = mapDeviceIdToControllerEnum(deviceId);
if (RBX::UserInputService* userInputService = getInputService())
{
userInputService->safeFireGamepadConnected(controllerNum);
}
}
void RobloxInput::handleGamepadKeyCodeSupportChanged(int deviceId, int keyCode, bool supported)
{
RBX::InputObject::UserInputType gamepadEnum = mapDeviceIdToControllerEnum(deviceId);
RBX::KeyCode rbxKeycode = getKeyCodeFromAndroidButtonInt(keyCode);
if (rbxKeycode == RBX::SDLK_UNKNOWN)
{
rbxKeycode = getKeyCodeFromAndroidAxisInt(keyCode);
}
if (gamepadEnum != RBX::InputObject::TYPE_NONE)
{
boost::mutex::scoped_lock mutex(SupportedControllerKeyCodeMutex);
if (gamepadSupportedKeyCodes[gamepadEnum].get() == NULL)
{
gamepadSupportedKeyCodes[gamepadEnum].reset(new RBX::Reflection::ValueArray());
}
shared_ptr<RBX::Reflection::ValueArray> supportedKeyCodes = gamepadSupportedKeyCodes[gamepadEnum];
if (!supported)
{
for (RBX::Reflection::ValueArray::iterator iter = supportedKeyCodes->begin(); iter != supportedKeyCodes->end(); ++iter)
{
const RBX::Reflection::Variant iterVariant = (*iter);
if (iterVariant.isType<RBX::KeyCode>())
{
RBX::KeyCode iterKeyCode = iterVariant.cast<RBX::KeyCode>();
if (iterKeyCode == rbxKeycode)
{
supportedKeyCodes->erase(iter);
break;
}
}
}
}
else
{
supportedKeyCodes->push_back(rbxKeycode);
}
// this line is probably not necessary
gamepadSupportedKeyCodes[gamepadEnum] = supportedKeyCodes;
}
}
void RobloxInput::getSupportedGamepadKeyCodes(RBX::InputObject::UserInputType gamepadEnum)
{
if (RBX::UserInputService* inputService = getInputService())
{
boost::mutex::scoped_lock mutex(SupportedControllerKeyCodeMutex);
inputService->setSupportedGamepadKeyCodes(gamepadEnum, gamepadSupportedKeyCodes[gamepadEnum]);
}
}
// this is a thread safe way to post input to DataModel (we should be in RenderStep)
void RobloxInput::processControllerBufferMap()
{
BufferedGamepadStates tempControllerBufferMap;
{
// grab our current input and process
// need a mutex in case Android is simultaneously trying to update map
boost::mutex::scoped_lock mutex(ControllerBufferMutex);
controllerBufferMap.swap(tempControllerBufferMap);
}
for (int gamepadNum = RBX::InputObject::TYPE_GAMEPAD1; gamepadNum != (RBX::InputObject::TYPE_GAMEPAD4 + 1); ++gamepadNum)
{
RBX::InputObject::UserInputType gamepadEnum = (RBX::InputObject::UserInputType) gamepadNum;
BufferedGamepadState gamepadBufferState = tempControllerBufferMap[gamepadEnum];
if (RBX::GamepadService* gamepadService = getGamepadService())
{
RBX::Gamepad rbxGamepad = gamepadService->getGamepadState(RBX::GamepadService::getGamepadIntForEnum(gamepadEnum));
for (BufferedGamepadState::iterator iter = gamepadBufferState.begin(); iter != gamepadBufferState.end(); ++iter)
{
boost::shared_ptr<RBX::InputObject> keyInputObject = rbxGamepad[(*iter).first];
std::vector<RBX::Vector3> positions = (*iter).second;
for (std::vector<RBX::Vector3>::iterator vecIter = positions.begin(); vecIter != positions.end(); ++vecIter)
{
bool isButton = false;
RBX::InputObject::UserInputState state = RBX::InputObject::INPUT_STATE_NONE;
switch (keyInputObject->getKeyCode())
{
case RBX::SDLK_GAMEPAD_BUTTONR2:
case RBX::SDLK_GAMEPAD_BUTTONL2:
{
if ((*vecIter).z >= 1.0f)
{
state = RBX::InputObject::INPUT_STATE_BEGIN;
}
else if ((*vecIter).z <= 0.0f)
{
state = RBX::InputObject::INPUT_STATE_END;
}
else
{
state = RBX::InputObject::INPUT_STATE_CHANGE;
}
break;
}
case RBX::SDLK_GAMEPAD_BUTTONA:
case RBX::SDLK_GAMEPAD_BUTTONB:
case RBX::SDLK_GAMEPAD_BUTTONX:
case RBX::SDLK_GAMEPAD_BUTTONY:
case RBX::SDLK_GAMEPAD_BUTTONR1:
case RBX::SDLK_GAMEPAD_BUTTONL1:
case RBX::SDLK_GAMEPAD_BUTTONR3:
case RBX::SDLK_GAMEPAD_BUTTONL3:
case RBX::SDLK_GAMEPAD_BUTTONSTART:
case RBX::SDLK_GAMEPAD_DPADDOWN:
case RBX::SDLK_GAMEPAD_DPADUP:
case RBX::SDLK_GAMEPAD_DPADLEFT:
case RBX::SDLK_GAMEPAD_DPADRIGHT:
{
isButton = true;
state = ((*vecIter).z > 0.0f) ? state = RBX::InputObject::INPUT_STATE_BEGIN : state = RBX::InputObject::INPUT_STATE_END;
break;
}
case RBX::SDLK_GAMEPAD_THUMBSTICK1:
case RBX::SDLK_GAMEPAD_THUMBSTICK2:
{
state = (*vecIter) == RBX::Vector3::zero() ? RBX::InputObject::INPUT_STATE_END : RBX::InputObject::INPUT_STATE_CHANGE;
break;
}
default:
{
break;
}
}
if (state != RBX::InputObject::INPUT_STATE_NONE)
{
RBX::Vector3 newPosition = (*vecIter);
if (isButton) // don't use pressure sensitive values to keep consistent behavior on all platforms
{
(newPosition.z > 0.0f) ? newPosition.z = 1.0f : newPosition.z = 0.0f;
}
bool shouldFireEvent = true;
if (keyInputObject->getRawPosition() != newPosition)
{
keyInputObject->setDelta((newPosition - keyInputObject->getRawPosition()));
keyInputObject->setPosition(newPosition);
}
else
{
shouldFireEvent = false;
}
if (state != RBX::InputObject::INPUT_STATE_NONE)
{
keyInputObject->setInputState(state);
}
if (shouldFireEvent)
{
if (RBX::UserInputService* inputService = getInputService())
{
inputService->dangerousFireInputEvent(keyInputObject, NULL);
}
}
}
}
}
}
}
}
void RobloxInput::processEvent(int eventId, const int xPos, const int yPos,
const int eventType, const float winSizeX, const float winSizeY)
{
if(RBX::TouchInputService* touchInputService = getTouchService())
{
RBX::Vector3 rbxLocation = RBX::Vector3(xPos, yPos, 0);
RBX::InputObject::UserInputState newState = RBX::InputObject::INPUT_STATE_NONE;
switch (eventType)
{
case 0:
newState = RBX::InputObject::INPUT_STATE_BEGIN;
if (tapEventId < 0)
{
tapEventId = eventId;
tapTouchBeginPos = RBX::Vector2(xPos, yPos);
tapLocation = RBX::Vector3(tapTouchBeginPos, 0);
tapTimer.reset();
}
break;
case 1:
newState = RBX::InputObject::INPUT_STATE_CHANGE;
if (tapEventId == eventId)
{
if ((RBX::Vector2(xPos, yPos) - tapTouchBeginPos).length() > tapTouchMoveTolerance)
{
tapEventId = -1;
tapLocation = RBX::Vector3::zero();
}
}
break;
case 2:
newState = RBX::InputObject::INPUT_STATE_END;
if (tapEventId == eventId)
{
if (tapTimer.delta().seconds() <= tapSensitivity)
{
sendWorkspaceEvent(rbxLocation);
}
tapEventId = -1;
tapLocation = RBX::Vector3::zero();
}
break;
}
touchInputService->addTouchToBuffer((void*) eventId, rbxLocation, newState);
}
}
void RobloxInput::sendAccelerometerEvent(float x,float y, float z)
{
if (RBX::UserInputService* inputService = getInputService())
{
// Measure in g's instead of m/s^2 to be consistent with iOS
inputService->fireAccelerationEvent( (RBX::Vector3(x, y, z)/GRAVITY_ACCELERATION) );
}
}
void RobloxInput::sendGravityEvent(float x,float y, float z)
{
if (RBX::UserInputService* inputService = getInputService())
{
// Measure in g's instead of m/s^2 to be consistent with iOS
inputService->fireGravityEvent( (RBX::Vector3(x, y, z)/GRAVITY_ACCELERATION));
}
}
void RobloxInput::sendGyroscopeEvent(float eulerX,float eulerY,float eulerZ,
float quaternionX, float quaternionY, float quaternionZ, float quaternionW)
{
if (RBX::UserInputService* inputService = getInputService())
{
inputService->fireRotationEvent( RBX::Vector3(eulerX,eulerY,eulerZ), RBX::Vector4(quaternionX,quaternionY,quaternionZ,quaternionW) );
}
}
void RobloxInput::setAccelerometerEnabled(bool enabled)
{
if (RBX::DataModel* dm = getDataModel())
{
dm->submitTask([=](...){
if (RBX::UserInputService* inputService = getInputService())
{
inputService->setAccelerometerEnabled(enabled);
}
}, RBX::DataModelJob::Write);
}
}
void RobloxInput::setGyroscopeEnabled(bool enabled)
{
if (RBX::DataModel* dm = getDataModel())
{
dm->submitTask([=](...){
if (RBX::UserInputService* inputService = getInputService())
{
inputService->setGyroscopeEnabled(enabled);
}
}, RBX::DataModelJob::Write);
}
}
+87
View File
@@ -0,0 +1,87 @@
#include <boost/unordered_map.hpp>
#include "V8DataModel/DataModel.h"
#include "V8DataModel/InputObject.h"
#include "V8DataModel/TextBox.h"
namespace RBX
{
class GamepadService;
class TouchInputService;
}
static boost::mutex ControllerBufferMutex;
static boost::mutex SupportedControllerKeyCodeMutex;
class RobloxInput
{
typedef std::vector<G3D::Vector3> KeycodeInputs;
typedef boost::unordered_map<RBX::KeyCode, KeycodeInputs> BufferedGamepadState;
typedef boost::unordered_map<RBX::InputObject::UserInputType, BufferedGamepadState> BufferedGamepadStates;
public:
RobloxInput();
static RobloxInput& getRobloxInput()
{
static RobloxInput robloxInput;
return robloxInput;
}
void processControllerBufferMap();
void processEvent(int eventId, const int xPos, const int yPos, const int eventType, const float winSizeX, const float winSizeY);
void sendGestureEvent(RBX::UserInputService::Gesture gestureToSend, shared_ptr<RBX::Reflection::Tuple> args, shared_ptr<RBX::Reflection::ValueArray> touchLocations);
void passTextInput(RBX::TextBox* textBox, std::string newText, bool enterPressed, int cursorPosition);
void externalReleaseFocus(RBX::TextBox* currentTextBox);
void sendAccelerometerEvent(float x,float y, float z);
void sendGyroscopeEvent(float eulerX,float eulerY,float eulerZ,
float quaternionX, float quaternionY, float quaternionZ, float quaternionW);
void sendGravityEvent(float x,float y, float z);
void setAccelerometerEnabled(bool enabled);
void setGyroscopeEnabled(bool enabled);
void handleGamepadConnect(int deviceId);
void handleGamepadDisconnect(int deviceId);
void handleGamepadButtonInput(int deviceId, int keyCode, int buttonState);
void handleGamepadAxisInput(int deviceId, int actionType, float newValueX, float newValueY, float newValueZ);
void getSupportedGamepadKeyCodes(RBX::InputObject::UserInputType gamepadEnum);
void handleGamepadKeyCodeSupportChanged(int deviceId, int keyCode, bool supported);
private:
shared_ptr<RBX::InputObject> tapInputObject;
RBX::Vector3 tapLocation;
int tapEventId;
weak_ptr<RBX::TouchInputService> weakTouchInputService;
weak_ptr<RBX::GamepadService> weakGamepadService;
G3D::Vector2 tapTouchBeginPos;
RBX::Timer<RBX::Time::Fast> tapTimer;
typedef std::map<RBX::InputObject::UserInputType, bool> ConnectedControllerMap;
ConnectedControllerMap connectedControllerMap;
boost::unordered_map<int, RBX::InputObject::UserInputType> deviceIdToGamepadId;
BufferedGamepadStates controllerBufferMap;
boost::unordered_map<RBX::InputObject::UserInputType, shared_ptr<RBX::Reflection::ValueArray> > gamepadSupportedKeyCodes;
RBX::DataModel* getDataModel();
RBX::UserInputService* getInputService();
RBX::TouchInputService* getTouchService();
RBX::GamepadService* getGamepadService();
RBX::InputObject::UserInputType mapDeviceIdToControllerEnum(int deviceId);
void sendWorkspaceEvent(shared_ptr<RBX::InputObject> inputObject);
void sendWorkspaceEvent(RBX::Vector3 touchPosition);
void postEventProcessed(const shared_ptr<RBX::Instance>& event, bool processedEvent);
};
+1
View File
@@ -0,0 +1 @@
#include "RobloxUtilities.h"
+31
View File
@@ -0,0 +1,31 @@
#ifndef ROBLOXUTILITIES_H
#define ROBLOXUTILITIES_H
#include "rbx/rbxTime.h"
class RobloxUtilities
{
public:
static RobloxUtilities& getRobloxUtilities()
{
static RobloxUtilities robloxUtilities;
return robloxUtilities;
}
RBX::Timer<RBX::Time::Fast>& getGameTimer() { return gameTimer; };
private:
RobloxUtilities() {};
RobloxUtilities(RobloxUtilities const&);
void operator=(RobloxUtilities const&);
RBX::Timer<RBX::Time::Fast> gameTimer;
};
#endif // ROBLOXUTILITIES_H
+645
View File
@@ -0,0 +1,645 @@
#include "RobloxView.h"
#include "GfxBase/ViewBase.h"
#include "v8datamodel/BaseRenderJob.h"
#include "v8datamodel/workspace.h"
#include "v8datamodel/camera.h"
#include "v8datamodel/game.h"
#include "FunctionMarshaller.h"
#include "Util/StandardOut.h"
#include "Util/FileSystem.h"
#include "rbx/Tasks/Coordinator.h"
#include "Util/IMetric.h"
#include "Util/Object.h"
#include "GfxBase/RenderSettings.h"
#include "GfxBase/FrameRateManager.h"
#include "v8datamodel/UserController.h"
#include "Util/Statistics.h"
#include "v8datamodel/ContentProvider.h"
#include "script/ScriptContext.h"
#include "v8xml/Serializer.h"
#include "rbx/CEvent.h"
#include "../RobloxMac/GameVerbs.h"
#include "Network/Players.h"
#include "../ClientBase/RenderSettingsItem.h"
#include "rbx/SystemUtil.h"
#include "JNIMain.h"
#include "JNIGLActivity.h"
#include <boost/iostreams/copy.hpp>
#include "../RobloxMac/Roblox.h"
#include "V8DataModel/GameBasicSettings.h"
#include "FastLog.h"
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
FASTFLAG(RenderLowLatencyLoop)
namespace RBX
{
LeaveGameVerb::LeaveGameVerb(RBX::VerbContainer* container) :
Verb(container, "Exit")
{
}
void LeaveGameVerb::doIt(RBX::IDataState* dataState)
{
JNI::exitGame();
}
namespace JNI
{
extern char *cacheDirectory;
} // namespace JNI
} // namespace RBX
LOGGROUP(RenderBreakdown)
FASTFLAGVARIABLE(RenderCleanupInBackground, true)
FASTFLAGVARIABLE(EnableiOSSettingsLeave, false)
// 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;
RBX::CEvent prepareBeginEvent;
RBX::CEvent prepareEndEvent;
volatile int stopped;
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)
, prepareBeginEvent(false)
, prepareEndEvent(false)
, stopped(0)
{
cyclicExecutive = 1;
}
RBX::Time::Interval sleepTime(const Stats& stats)
{
if(isAwake)
return computeStandardSleepTime(stats, CRenderSettingsItem::singleton().getMaxFrameRate());
else
return RBX::Time::Interval::max();
}
void stop()
{
stopped = 1;
}
static void scheduleRender(weak_ptr<RenderJob> selfWeak, ViewBase* view, double timeJobStart)
{
shared_ptr<RenderJob> self = selfWeak.lock();
if (!self) return;
self->prepareBeginEvent.Wait();
view->renderPrepare(self.get());
self->prepareEndEvent.Set();
view->renderPerform(timeJobStart);
self->wake();
}
static void scheduleRenderPrepare(RenderJob* self, ViewBase* view)
{
if (self->stopped != 0)
return;
view->renderPrepare(self);
}
static void scheduleRenderPerform(RenderJob* self, ViewBase* view, double timeJobStart)
{
if( !self->dataModel.lock() )
return;
if ( self->stopped != 0 )
return;
if(!view)
return;
view->renderPerform(timeJobStart);
self->wake();
}
virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
{
shared_ptr<RBX::DataModel> dm(dataModel.lock());
if (!dm || stopped)
return RBX::TaskScheduler::Done;
// Initially a view does not have a data model; it gets one during bindWorkspace.
// If bindWorkspace is launched asynchronously, there is a possibility of a race -
// render job might start before bindWorkspace gets a chance to run.
// It should be safe to skip the render job in this case.
if (!view->getDataModel())
return RBX::TaskScheduler::Stepped;
double timeJobStart = Time::nowFastSec();
if (FFlag::RenderLowLatencyLoop)
{
RBX::DataModel::scoped_write_request request(dm.get());
const double renderDelta = timeSinceLastRender().seconds();
lastRenderTime = RBX::Time::now<RBX::Time::Fast>();
isAwake = false;
marshaller->Submit(boost::bind(&scheduleRender, weak_from(this), view, timeJobStart));
view->updateVR();
dm->renderStep(renderDelta);
prepareBeginEvent.Set();
prepareEndEvent.Wait();
}
else
{
try
{
{
RBX::DataModel::scoped_write_request request(dm.get());
const double renderDelta = timeSinceLastRender().seconds();
lastRenderTime = RBX::Time::now<RBX::Time::Fast>();
view->updateVR();
dm->renderStep(renderDelta);
isAwake = false;
FASTLOG(FLog::RenderBreakdown, "Trigger renderPrepare");
marshaller->Execute(boost::bind(&scheduleRenderPrepare, this, view), &renderEvent);
FASTLOG(FLog::RenderBreakdown, "Finished renderPrepare");
}
{
FASTLOG(FLog::RenderBreakdown, "Trigger renderPerform");
marshaller->Submit(boost::bind(&scheduleRenderPerform, this, view, timeJobStart));
FASTLOG(FLog::RenderBreakdown, "Finished renderPerform");
}
}
catch (RBX::base_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 Job Time")
return averageStepTime();
if (metric == "Render Nominal FPS")
return frm ? 1000.0 / frm->GetRenderTimeAverage() : 0.0;
if (metric == "Delta Between Renders")
return view->getMetricValue(metric);
if (metric == "Total Render")
return view->getMetricValue(metric);
if (metric == "Present Time")
return view->getMetricValue(metric);
if (metric == "GPU Delay")
return view->getMetricValue(metric);
if (metric == "Render Prepare")
return view->getMetricValue(metric);
if (metric == "Video Memory MB")
return RBX::SystemUtil::getVideoMemory() / 1e6;
return 0.0;
}
/*override*/ std::string getMetric(const std::string& metric) const
{
if (! view )
return "No View";
if (metric == "Graphics Mode")
return "";
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 "";
}
};
void RobloxView::requestStopRenderingForBackgroundMode()
{
if (FFlag::RenderCleanupInBackground)
{
if (renderJob)
{
renderJob->abortRender();
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
RBX::TaskScheduler::singleton().removeBlocking(renderJob, 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();
}
else
{
if (renderJob)
{
renderJob->abortRender();
renderJob->stop();
renderJob.reset();
}
}
}
void RobloxView::requestResumeRendering()
{
renderJob = shared_ptr<RenderJob>(new RenderJob(view.get(), marshaller, game->getDataModel()));
RBX::TaskScheduler::singleton().add(renderJob);
}
static RBX::ViewBase* createGameWindow(RobloxView* view, void *wnd, unsigned int width, unsigned int height)
{
static boost::once_flag flag2 = BOOST_ONCE_INIT;
// 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 = width;
context.height = height;
CRenderSettingsItem& settings = CRenderSettingsItem::singleton();
RBX::CRenderSettings::GraphicsMode mode = RBX::CRenderSettings::OpenGL;
RBX::ViewBase* rbxView = RBX::ViewBase::CreateView(mode, &context, &settings);
rbxView->initResources();
return rbxView;
}
RobloxView::RobloxView(void* wnd, unsigned int width, unsigned int height)
:view(createGameWindow(this, wnd, width, height))
,marshaller(RBX::FunctionMarshaller::GetWindow())
{
}
void RobloxView::completeViewPrep(shared_ptr<RBX::Game> game)
{
this->game = game;
placeIDChangeConnection = game->getDataModel()->propertyChangedSignal.connect( boost::bind(&RobloxView::onPlaceIDChanged, this, _1) );
shared_ptr<DataModel> dataModelToSubmitOn = game->getDataModel();
{
RBX::DataModel::LegacyLock lock(dataModelToSubmitOn.get(), RBX::DataModelJob::Write);
if( RBX::UserInputService* userInputService = RBX::ServiceProvider::create<RBX::UserInputService>(dataModelToSubmitOn.get()) )
{
userInputService->setTouchEnabled(true);
}
}
bindWorkspace(view, game->getDataModel());
// complete render jobs setup
renderJob = shared_ptr<RenderJob>(new RenderJob(view.get(), marshaller, game->getDataModel()));
defineConcurrencyRules();
// Important! only schedule view and render jobs after concurrency rules are defined
RBX::TaskScheduler::singleton().add(renderJob);
if (shared_ptr<RBX::DataModel> sharedDM = game->getDataModel())
{
if (RBX::DataModel* dm = sharedDM.get())
{
leaveGameVerb.reset(new RBX::LeaveGameVerb(dm) );
}
}
}
void RobloxView::replaceGame(shared_ptr<RBX::Game> game)
{
// Shut down
if (sequence)
{
if (RBX::RunService* rs = game->getDataModel()->find<RBX::RunService>())
rs->getPhysicsJob()->removeCoordinator(sequence);
}
if (renderJob)
{
renderJob->abortRender();
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
RBX::TaskScheduler::singleton().removeBlocking(renderJob, 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();
if (boost::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->setIsShuttingDown(true);
}
{
RBX::DataModel::LegacyLock lock(game->getDataModel().get(), RBX::DataModelJob::Write);
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(game->getDataModel().get());
service->setHardwareDevice(NULL);
view->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
}
// Setup the new game
this->game = game;
// Necessary to insure we have touch controls after a teleport
if(DataModel* dm = game->getDataModel().get())
{
if(RBX::UserInputService* inputService = RBX::ServiceProvider::create<RBX::UserInputService>(dm))
{
inputService->setTouchEnabled(true);
inputService->textBoxGainFocus.connect( boost::bind(&JNI::textBoxFocused, _1) );
inputService->motionEventListeningStarted.connect( boost::bind(&JNI::motionEventListening, _1) );
inputService->textBoxReleaseFocus.connect( boost::bind(&JNI::textBoxFocusLost, _1) );
}
leaveGameVerb.reset(new RBX::LeaveGameVerb(dm) );
}
placeIDChangeConnection = game->getDataModel()->propertyChangedSignal.connect( boost::bind(&RobloxView::onPlaceIDChanged, this, _1) );
bindWorkspace(view, game->getDataModel());
// complete render jobs setup
renderJob = shared_ptr<RenderJob>(new RenderJob(view.get(), marshaller, game->getDataModel()));
defineConcurrencyRules();
// Important! only schedule view and render jobs after concurrency rules are defined
RBX::TaskScheduler::singleton().add(renderJob);
}
void RobloxView::restartDataModel()
{
doRestartDataModel();
}
void RobloxView::doRestartDataModel()
{
// dispatch_async( dispatch_get_main_queue(), ^{
{
if (RBX::RunService* rs = game->getDataModel()->find<RBX::RunService>())
rs->stopTasks();
}
renderJob->abortRender();
renderJob->stop();
{
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
RBX::TaskScheduler::singleton().removeBlocking(renderJob, callback);
if (sequence)
{
if (RBX::RunService* rs = game->getDataModel()->find<RBX::RunService>())
rs->getPhysicsJob()->removeCoordinator(sequence);
}
}
// make sure all jobs have been completed, we are going to destroy game datamodel now
marshaller->ProcessMessages();
// All render processing is complete; it's safe to reset job pointers now
renderJob.reset();
{
RBX::DataModel::LegacyLock dmlock(game->getDataModel().get(), RBX::DataModelJob::Write);
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(game->getDataModel().get());
service->setHardwareDevice(NULL);
placeIDChangeConnection.disconnect();
}
game->shutdown();
setupNewDataModel();
// });
}
void RobloxView::setupNewDataModel()
{
if(game->getDataModel())
return;
{
RBX::DataModel::LegacyLock dmlock(game->getDataModel(), RBX::DataModelJob::Write);
view->bindWorkspace(game->getDataModel());
view->buildGui();
placeIDChangeConnection = game->getDataModel()->propertyChangedSignal.connect( boost::bind(&RobloxView::onPlaceIDChanged, this, _1) );
}
}
void RobloxView::newGameDidStart()
{
// dispatch_async( dispatch_get_main_queue(), ^{
// now we can start rendering again
requestResumeRendering();
// });
}
void RobloxView::onPlaceIDChanged(const RBX::Reflection::PropertyDescriptor* desc)
{
#if !RBX_PLATFORM_IOS
// bool placeIDChanged = desc->name=="PlaceId";
//
// if(placeIDChanged && dataModel->getPlaceID() > 0)
// Roblox::addBreakPadKeyValue("Place0", dataModel->getPlaceID());
#endif
}
void RobloxView::defineConcurrencyRules()
{
RBXASSERT(renderJob);
if (CRenderSettingsItem::singleton().isSynchronizedWithPhysics)
{
// Force rendering and physics to happen in lock-step
sequence.reset(new RBX::Tasks::Sequence());
renderJob->addCoordinator(sequence);
game->getDataModel()->create<RBX::RunService>()->getPhysicsJob()->addCoordinator(sequence);
}
}
RobloxView::~RobloxView(void)
{
if (sequence)
{
if (RBX::RunService* rs = game->getDataModel()->find<RBX::RunService>())
rs->getPhysicsJob()->removeCoordinator(sequence);
}
if (renderJob)
{
renderJob->abortRender();
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
RBX::TaskScheduler::singleton().removeBlocking(renderJob, 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();
if (boost::shared_ptr<RBX::DataModel> dataModel = game->getDataModel())
{
dataModel->setIsShuttingDown(true);
}
{
RBX::DataModel::LegacyLock lock(game->getDataModel().get(), RBX::DataModelJob::Write);
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(game->getDataModel().get());
service->setHardwareDevice(NULL);
view->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
}
RBX::FunctionMarshaller::ReleaseWindow(marshaller);
// First destroy the view before closing the DataModel
view.reset();
}
void RobloxView::bindWorkspace(boost::shared_ptr<RBX::ViewBase> view, boost::shared_ptr<RBX::DataModel> const dataModel, bool buildGUI)
{
DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write);
view->bindWorkspace(dataModel);
if(buildGUI)
view->buildGui();
}
void RobloxView::setBounds(unsigned int width, unsigned int height)
{
this->width = width; this->height = height;
if (view)
view->onResize(width, height);
}
RobloxView *RobloxView::create_view(shared_ptr<RBX::Game> game, void* wnd, unsigned int width, unsigned int height)
{
RobloxView* result = new RobloxView(wnd, width,height);
result->completeViewPrep(game);
return result;
}
void RobloxView::shutDownGraphics(shared_ptr<RBX::Game> game)
{
if (renderJob)
{
renderJob->abortRender();
renderJob->stop();
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
RBX::TaskScheduler::singleton().removeBlocking(renderJob, callback);
renderJob.reset();
}
marshaller->ProcessMessages();
view->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
RBX::FunctionMarshaller::ReleaseWindow(marshaller);
view.reset();
}
void RobloxView::startUpGraphics(shared_ptr<RBX::Game> game, void *wnd, unsigned int width, unsigned int height )
{
view = boost::shared_ptr<RBX::ViewBase>( createGameWindow(this, wnd, width, height) );
marshaller = RBX::FunctionMarshaller::GetWindow();
bindWorkspace(view, game->getDataModel(), false);
// complete render jobs setup
renderJob = shared_ptr<RenderJob>(new RenderJob(view.get(), marshaller, game->getDataModel()));
defineConcurrencyRules();
// Important! only schedule view and render jobs after concurrency rules are defined
RBX::TaskScheduler::singleton().add(renderJob);
}
+96
View File
@@ -0,0 +1,96 @@
#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"
#include "v8tree/Verb.h"
namespace RBX
{
class DataModel;
class ViewBase;
class FunctionMarshaller;
namespace Tasks
{
class Sequence;
}
namespace Reflection
{
class PropertyDescriptor;
}
class LeaveGameVerb : public RBX::Verb
{
public :
LeaveGameVerb(RBX::VerbContainer* container);
virtual void doIt(RBX::IDataState* dataState);
};
}
class RobloxView
{
boost::scoped_ptr<class RBX::LeaveGameVerb> leaveGameVerb;
boost::shared_ptr<RBX::ViewBase> view;
boost::shared_ptr<RBX::Game> game;
RBX::FunctionMarshaller* marshaller;
rbx::signals::scoped_connection placeIDChangeConnection;
boost::shared_ptr<RBX::Tasks::Sequence> sequence;
class RenderJob;
boost::shared_ptr<RenderJob> renderJob;
static boost::shared_ptr<RobloxView> rbxView;
void doTeleport(std::string url, std::string ticket, std::string script);
void onPlaceIDChanged(const RBX::Reflection::PropertyDescriptor* desc);
public:
RobloxView(void* wnd, unsigned int width, unsigned int height);
~RobloxView(void);
// request rendering stop as the app goes to background
void requestStopRenderingForBackgroundMode();
void requestResumeRendering();
void newGameDidStart();
void setBounds(unsigned int width, unsigned int height);
static RobloxView *create_view(shared_ptr<RBX::Game> game, void* wnd, unsigned int width, unsigned int height);
boost::shared_ptr<RBX::DataModel> getDataModel() { return game->getDataModel(); }
boost::shared_ptr<RBX::Game> getGame() { return game; }
boost::shared_ptr<RBX::ViewBase> getView() { return view; }
void restartDataModel();
G3D::Vector2 getBounds() { return G3D::Vector2(width,height); }
void replaceGame(shared_ptr<RBX::Game> game);
void shutDownGraphics(shared_ptr<RBX::Game> game);
void startUpGraphics(shared_ptr<RBX::Game> game, void *wnd, unsigned int width, unsigned int height);
private:
unsigned int width;
unsigned int height;
void defineConcurrencyRules();
void setupNewDataModel();
void doRestartDataModel();
static void bindWorkspace(boost::shared_ptr<RBX::ViewBase> view, boost::shared_ptr<RBX::DataModel> const dataModel, bool buildGUI = true);
void completeViewPrep(shared_ptr<RBX::Game> game);
};
+7
View File
@@ -0,0 +1,7 @@
#include "Teleporter.h"
#include "PlaceLauncher.h"
void Teleporter::teleportImpl(std::string url, std::string ticket, std::string script)
{
PlaceLauncher::getPlaceLauncher().teleport(ticket, url, script);
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "v8datamodel/TeleportCallback.h"
#include "FunctionMarshaller.h"
class PlaceLauncher;
class Teleporter: public RBX::TeleportCallback
{
RBX::FunctionMarshaller* mMarshaller;
static void teleportImpl(std::string url, std::string ticket, std::string script);
public:
Teleporter(RBX::FunctionMarshaller* marshaller): mMarshaller(marshaller)
{
}
virtual void doTeleport(const std::string& url, const std::string& ticket, const std::string& script)
{
mMarshaller->Submit(boost::bind(teleportImpl, url, ticket, script));
}
virtual bool isTeleportEnabled() const { return true; }
};