mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-06 05:37:48 +00:00
GEEKING
This commit is contained in:
@@ -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__ */
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "RobloxInfo.h"
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include "RobloxUtilities.h"
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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; }
|
||||
};
|
||||
Reference in New Issue
Block a user