1339 lines
46 KiB
C++
Raw Normal View History

2022-06-25 00:23:03 +08:00
/****************************************************************************
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "jsb_global.h"
#include "jsb_conversions.hpp"
#include "xxtea/xxtea.h"
#include "base/CCScheduler.h"
#include "base/CCThreadPool.h"
#include "network/HttpClient.h"
#include "platform/CCApplication.h"
#include "ui/edit-box/EditBox.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
#include "platform/android/jni/JniImp.h"
#endif
#include <regex>
using namespace cocos2d;
se::Object* __jsbObj = nullptr;
se::Object* __glObj = nullptr;
static std::shared_ptr<ThreadPool> g_threadPool;
static std::shared_ptr<cocos2d::network::Downloader> g_localDownloader = nullptr;
static std::map<std::string, std::function<void(const std::string&, unsigned char*, int ,const std::string&)>> g_localDownloaderHandlers;
static uint64_t g_localDownloaderTaskId = 1000000;
static std::string xxteaKey = "";
void jsb_set_xxtea_key(const std::string& key)
{
xxteaKey = key;
}
static cocos2d::network::Downloader *localDownloader()
{
if(!g_localDownloader)
{
g_localDownloader = std::make_shared<cocos2d::network::Downloader>();
g_localDownloader->onDataTaskSuccess = [=](const cocos2d::network::DownloadTask& task,
std::vector<unsigned char>& data) {
if(data.empty())
{
SE_REPORT_ERROR("Getting image from (%s) failed!", task.requestURL.c_str());
return;
}
auto callback = g_localDownloaderHandlers.find(task.identifier);
if(callback == g_localDownloaderHandlers.end())
{
SE_REPORT_ERROR("Getting image from (%s), callback not found!!", task.requestURL.c_str());
return;
}
size_t imageBytes = data.size();
unsigned char* imageData = (unsigned char*)malloc(imageBytes);
memcpy(imageData, data.data(), imageBytes);
(callback->second)("", imageData, imageBytes, "");
//initImageFunc("", imageData, imageBytes);
g_localDownloaderHandlers.erase(callback);
};
g_localDownloader->onTaskError = [=](const cocos2d::network::DownloadTask& task,
int errorCode,
int errorCodeInternal,
const std::string& errorStr) {
SE_REPORT_ERROR("Getting image from (%s) failed!", task.requestURL.c_str());
auto callback = g_localDownloaderHandlers.find(task.identifier);
if(callback == g_localDownloaderHandlers.end())
{
SE_REPORT_ERROR("Getting image from (%s), callback not found!!", task.requestURL.c_str());
return;
}
(callback->second)("", nullptr, 0, errorStr);
g_localDownloaderHandlers.erase(task.identifier);
};
}
return g_localDownloader.get();
}
static void localDownloaderCreateTask(const std::string &url, std::function<void(const std::string&, unsigned char*, int, const std::string&)> callback)
{
std::stringstream ss;
ss << "jsb_loadimage_" << (g_localDownloaderTaskId++);
std::string key = ss.str();
auto task = localDownloader()->createDownloadDataTask(url, key);
g_localDownloaderHandlers.emplace(std::make_pair(task->identifier, callback));
}
static const char* BYTE_CODE_FILE_EXT = ".jsc";
static std::string removeFileExt(const std::string& filePath)
{
size_t pos = filePath.rfind('.');
if (0 < pos)
{
return filePath.substr(0, pos);
}
return filePath;
}
void jsb_init_file_operation_delegate()
{
static se::ScriptEngine::FileOperationDelegate delegate;
if (!delegate.isValid())
{
delegate.onGetDataFromFile = [](const std::string& path, const std::function<void(const uint8_t*, size_t)>& readCallback) -> void{
assert(!path.empty());
Data fileData;
std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
if (FileUtils::getInstance()->isFileExist(byteCodePath)) {
fileData = FileUtils::getInstance()->getDataFromFile(byteCodePath);
size_t dataLen = 0;
uint8_t* data = xxtea_decrypt((unsigned char*)fileData.getBytes(), (uint32_t)fileData.getSize(), (unsigned char*)xxteaKey.c_str(), (uint32_t)xxteaKey.size(), (uint32_t*)&dataLen);
if (data == nullptr) {
SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str());
return;
}
if (ZipUtils::isGZipBuffer(data,dataLen)) {
uint8_t* unpackedData;
ssize_t unpackedLen = ZipUtils::inflateMemory(data, dataLen,&unpackedData);
if (unpackedData == nullptr) {
SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str());
return;
}
readCallback(unpackedData, unpackedLen);
free(data);
free(unpackedData);
}
else {
readCallback(data, dataLen);
free(data);
}
return;
}
fileData = FileUtils::getInstance()->getDataFromFile(path);
readCallback(fileData.getBytes(), fileData.getSize());
};
delegate.onGetStringFromFile = [](const std::string& path) -> std::string{
assert(!path.empty());
std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
if (FileUtils::getInstance()->isFileExist(byteCodePath)) {
Data fileData = FileUtils::getInstance()->getDataFromFile(byteCodePath);
uint32_t dataLen;
uint8_t* data = xxtea_decrypt((uint8_t*)fileData.getBytes(), (uint32_t)fileData.getSize(), (uint8_t*)xxteaKey.c_str(), (uint32_t)xxteaKey.size(), &dataLen);
if (data == nullptr) {
SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str());
return "";
}
if (ZipUtils::isGZipBuffer(data,dataLen)) {
uint8_t* unpackedData;
ssize_t unpackedLen = ZipUtils::inflateMemory(data, dataLen,&unpackedData);
if (unpackedData == nullptr) {
SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str());
return "";
}
std::string ret(reinterpret_cast<const char*>(unpackedData), unpackedLen);
free(unpackedData);
free(data);
return ret;
}
else {
std::string ret(reinterpret_cast<const char*>(data), dataLen);
free(data);
return ret;
}
}
if (FileUtils::getInstance()->isFileExist(path)) {
return FileUtils::getInstance()->getStringFromFile(path);
}
else {
SE_LOGE("ScriptEngine::onGetStringFromFile %s not found, possible missing file.\n", path.c_str());
}
return "";
};
delegate.onGetFullPath = [](const std::string& path) -> std::string{
assert(!path.empty());
std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
if (FileUtils::getInstance()->isFileExist(byteCodePath)) {
return FileUtils::getInstance()->fullPathForFilename(byteCodePath);
}
return FileUtils::getInstance()->fullPathForFilename(path);
};
delegate.onCheckFileExist = [](const std::string& path) -> bool{
assert(!path.empty());
return FileUtils::getInstance()->isFileExist(path);
};
assert(delegate.isValid());
}
se::ScriptEngine::getInstance()->setFileOperationDelegate(delegate);
}
bool jsb_enable_debugger(const std::string& debuggerServerAddr, uint32_t port, bool isWaitForConnect)
{
if (debuggerServerAddr.empty() || port == 0)
return false;
auto se = se::ScriptEngine::getInstance();
se->enableDebugger(debuggerServerAddr.c_str(), port, isWaitForConnect);
// For debugger main loop
class SimpleRunLoop
{
public:
void update(float dt)
{
se::ScriptEngine::getInstance()->mainLoopUpdate();
}
};
// static SimpleRunLoop runLoop;
//cjh IDEA: Director::getInstance()->getScheduler()->scheduleUpdate(&runLoop, 0, false);
return true;
}
bool jsb_set_extend_property(const char* ns, const char* clsName)
{
se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject();
se::Value nsVal;
if (globalObj->getProperty(ns, &nsVal) && nsVal.isObject())
{
se::Value ccVal;
if (globalObj->getProperty("cc", &ccVal) && ccVal.isObject())
{
se::Value ccClassVal;
if (ccVal.toObject()->getProperty("Class", &ccClassVal) && ccClassVal.isObject())
{
se::Value extendVal;
if (ccClassVal.toObject()->getProperty("extend", &extendVal) && extendVal.isObject() && extendVal.toObject()->isFunction())
{
se::Value targetClsVal;
if (nsVal.toObject()->getProperty(clsName, &targetClsVal) && targetClsVal.isObject())
{
return targetClsVal.toObject()->setProperty("extend", extendVal);
}
}
}
}
}
return false;
}
namespace {
std::unordered_map<std::string, se::Value> __moduleCache;
#if (CC_TARGET_PLATFORM != CC_PLATFORM_OPENHARMONY)
static bool require(se::State& s)
{
const auto& args = s.args();
int argc = (int)args.size();
assert(argc >= 1);
assert(args[0].isString());
return jsb_run_script(args[0].toString(), &s.rval());
}
SE_BIND_FUNC(require)
#else
static bool run_script(se::State& s)
{
const auto& args = s.args();
int argc = (int)args.size();
assert(argc >= 1);
assert(args[0].isString());
return jsb_run_script(args[0].toString(), &s.rval());
}
SE_BIND_FUNC(run_script)
#endif
2022-06-25 00:23:03 +08:00
static bool doModuleRequire(const std::string& path, se::Value* ret, const std::string& prevScriptFileDir)
{
se::AutoHandleScope hs;
assert(!path.empty());
const auto& fileOperationDelegate = se::ScriptEngine::getInstance()->getFileOperationDelegate();
assert(fileOperationDelegate.isValid());
std::string fullPath;
std::string pathWithSuffix = path;
if (pathWithSuffix.rfind(".js") != (pathWithSuffix.length() - 3))
pathWithSuffix += ".js";
std::string scriptBuffer = fileOperationDelegate.onGetStringFromFile(pathWithSuffix);
if (scriptBuffer.empty() && !prevScriptFileDir.empty())
{
std::string secondPath = prevScriptFileDir;
if (secondPath[secondPath.length()-1] != '/')
secondPath += "/";
secondPath += path;
if (FileUtils::getInstance()->isDirectoryExist(secondPath))
{
if (secondPath[secondPath.length()-1] != '/')
secondPath += "/";
secondPath += "index.js";
}
else
{
if (path.rfind(".js") != (path.length() - 3))
secondPath += ".js";
}
fullPath = fileOperationDelegate.onGetFullPath(secondPath);
scriptBuffer = fileOperationDelegate.onGetStringFromFile(fullPath);
}
else
{
fullPath = fileOperationDelegate.onGetFullPath(pathWithSuffix);
}
if (!scriptBuffer.empty())
{
const auto& iter = __moduleCache.find(fullPath);
if (iter != __moduleCache.end())
{
*ret = iter->second;
// printf("Found cache: %s, value: %d\n", fullPath.c_str(), (int)ret->getType());
return true;
}
std::string currentScriptFileDir = FileUtils::getInstance()->getFileDir(fullPath);
// Add closure for evalutate the script
char prefix[] = "(function(currentScriptDir){ window.module = window.module || {}; var exports = window.module.exports = {}; ";
char suffix[512] = {0};
snprintf(suffix, sizeof(suffix), "\nwindow.module.exports = window.module.exports || exports;\n})('%s'); ", currentScriptFileDir.c_str());
// Add current script path to require function invocation
scriptBuffer = prefix + std::regex_replace(scriptBuffer, std::regex("([^A-Za-z0-9]|^)requireModule\\((.*?)\\)"), "$1requireModule($2, currentScriptDir)") + suffix;
// FILE* fp = fopen("/Users/james/Downloads/test.txt", "wb");
// fwrite(scriptBuffer.c_str(), scriptBuffer.length(), 1, fp);
// fclose(fp);
std::string reletivePath = fullPath;
#if CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_IOS
#if CC_TARGET_PLATFORM == CC_PLATFORM_MAC
const std::string reletivePathKey = "/Contents/Resources";
#else
const std::string reletivePathKey = ".app";
#endif
size_t pos = reletivePath.find(reletivePathKey);
if (pos != std::string::npos)
{
reletivePath = reletivePath.substr(pos + reletivePathKey.length() + 1);
}
#endif
// RENDERER_LOGD("Evaluate: %s", fullPath.c_str());
auto se = se::ScriptEngine::getInstance();
bool succeed = se->evalString(scriptBuffer.c_str(), scriptBuffer.length(), nullptr, reletivePath.c_str());
se::Value moduleVal;
if (succeed && se->getGlobalObject()->getProperty("module", &moduleVal) && moduleVal.isObject())
{
se::Value exportsVal;
if (moduleVal.toObject()->getProperty("exports", &exportsVal))
{
if (ret != nullptr)
*ret = exportsVal;
__moduleCache[fullPath] = std::move(exportsVal);
}
else
{
__moduleCache[fullPath] = se::Value::Undefined;
}
// clear module.exports
moduleVal.toObject()->setProperty("exports", se::Value::Undefined);
}
else
{
__moduleCache[fullPath] = se::Value::Undefined;
}
assert(succeed);
return succeed;
}
SE_LOGE("doModuleRequire %s, buffer is empty!\n", path.c_str());
assert(false);
return false;
}
#if (CC_TARGET_PLATFORM != CC_PLATFORM_OPENHARMONY)
static bool moduleRequire(se::State& s)
{
const auto& args = s.args();
int argc = (int)args.size();
assert(argc >= 2);
assert(args[0].isString());
assert(args[1].isString());
return doModuleRequire(args[0].toString(), &s.rval(), args[1].toString());
}
SE_BIND_FUNC(moduleRequire)
#endif
2022-06-25 00:23:03 +08:00
} // namespace {
bool jsb_run_script(const std::string& filePath, se::Value* rval/* = nullptr */)
{
se::AutoHandleScope hs;
return se::ScriptEngine::getInstance()->runScript(filePath, rval);
}
bool jsb_run_script_module(const std::string& filePath, se::Value* rval/* = nullptr */)
{
return doModuleRequire(filePath, rval, "");
}
static bool jsc_garbageCollect(se::State& s)
{
se::ScriptEngine::getInstance()->garbageCollect();
return true;
}
SE_BIND_FUNC(jsc_garbageCollect)
static bool jsc_dumpNativePtrToSeObjectMap(se::State& s)
{
cocos2d::log(">>> total: %d, Dump (native -> jsobj) map begin", (int)se::NativePtrToObjectMap::size());
struct NamePtrStruct
{
const char* name;
void* ptr;
};
std::vector<NamePtrStruct> namePtrArray;
for (const auto& e : se::NativePtrToObjectMap::instance())
{
se::Object* jsobj = e.second;
assert(jsobj->_getClass() != nullptr);
NamePtrStruct tmp;
tmp.name = jsobj->_getClass()->getName();
tmp.ptr = e.first;
namePtrArray.push_back(tmp);
}
std::sort(namePtrArray.begin(), namePtrArray.end(), [](const NamePtrStruct& a, const NamePtrStruct& b) -> bool {
std::string left = a.name;
std::string right = b.name;
for( std::string::const_iterator lit = left.begin(), rit = right.begin(); lit != left.end() && rit != right.end(); ++lit, ++rit )
if( ::tolower( *lit ) < ::tolower( *rit ) )
return true;
else if( ::tolower( *lit ) > ::tolower( *rit ) )
return false;
if( left.size() < right.size() )
return true;
return false;
});
for (const auto& e : namePtrArray)
{
cocos2d::log("%s: %p", e.name, e.ptr);
}
cocos2d::log(">>> total: %d, nonRefMap: %d, Dump (native -> jsobj) map end", (int)se::NativePtrToObjectMap::size(), (int)se::NonRefNativePtrCreatedByCtorMap::size());
return true;
}
SE_BIND_FUNC(jsc_dumpNativePtrToSeObjectMap)
static bool jsc_dumpRoot(se::State& s)
{
assert(false);
return true;
}
SE_BIND_FUNC(jsc_dumpRoot)
static bool JSBCore_platform(se::State& s)
{
Application::Platform platform = Application::getInstance()->getPlatform();
s.rval().setInt32((int32_t)platform);
return true;
}
SE_BIND_FUNC(JSBCore_platform)
static bool JSBCore_version(se::State& s)
{
//cjh char version[256];
// snprintf(version, sizeof(version)-1, "%s", cocos2dVersion());
//
// s.rval().setString(version);
return true;
}
SE_BIND_FUNC(JSBCore_version)
static bool JSBCore_os(se::State& s)
{
se::Value os;
// osx, ios, android, windows, linux, etc..
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
os.setString("iOS");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
os.setString("Android");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
os.setString("Windows");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MARMALADE)
os.setString("Marmalade");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
os.setString("Linux");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_BADA)
os.setString("Bada");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_BLACKBERRY)
os.setString("Blackberry");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
os.setString("OS X");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
os.setString("WINRT");
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_OPENHARMONY)
os.setString("OpenHarmony");
2022-06-25 00:23:03 +08:00
#else
os.setString("Unknown");
#endif
s.rval() = os;
return true;
}
SE_BIND_FUNC(JSBCore_os)
static bool JSBCore_getCurrentLanguage(se::State& s)
{
std::string languageStr;
Application::LanguageType language = Application::getInstance()->getCurrentLanguage();
switch (language)
{
case Application::LanguageType::ENGLISH:
languageStr = "en";
break;
case Application::LanguageType::CHINESE:
languageStr = "zh";
break;
case Application::LanguageType::FRENCH:
languageStr = "fr";
break;
case Application::LanguageType::ITALIAN:
languageStr = "it";
break;
case Application::LanguageType::GERMAN:
languageStr = "de";
break;
case Application::LanguageType::SPANISH:
languageStr = "es";
break;
case Application::LanguageType::DUTCH:
languageStr = "du";
break;
case Application::LanguageType::RUSSIAN:
languageStr = "ru";
break;
case Application::LanguageType::KOREAN:
languageStr = "ko";
break;
case Application::LanguageType::JAPANESE:
languageStr = "ja";
break;
case Application::LanguageType::HUNGARIAN:
languageStr = "hu";
break;
case Application::LanguageType::PORTUGUESE:
languageStr = "pt";
break;
case Application::LanguageType::ARABIC:
languageStr = "ar";
break;
case Application::LanguageType::NORWEGIAN:
languageStr = "no";
break;
case Application::LanguageType::POLISH:
languageStr = "pl";
break;
case Application::LanguageType::TURKISH:
languageStr = "tr";
break;
case Application::LanguageType::UKRAINIAN:
languageStr = "uk";
break;
case Application::LanguageType::ROMANIAN:
languageStr = "ro";
break;
case Application::LanguageType::BULGARIAN:
languageStr = "bg";
break;
default:
languageStr = "unknown";
break;
}
s.rval().setString(languageStr);
return true;
}
SE_BIND_FUNC(JSBCore_getCurrentLanguage)
static bool JSBCore_getCurrentLanguageCode(se::State& s)
{
std::string language = Application::getInstance()->getCurrentLanguageCode();
s.rval().setString(language);
return true;
}
SE_BIND_FUNC(JSBCore_getCurrentLanguageCode)
static bool JSB_getOSVersion(se::State& s)
{
std::string systemVersion = Application::getInstance()->getSystemVersion();
s.rval().setString(systemVersion);
return true;
}
SE_BIND_FUNC(JSB_getOSVersion)
static bool JSB_cleanScript(se::State& s)
{
assert(false); //IDEA:
return true;
}
SE_BIND_FUNC(JSB_cleanScript)
static bool JSB_core_restartVM(se::State& s)
{
//REFINE: release AudioEngine, waiting HttpClient & WebSocket threads to exit.
Application::getInstance()->restart();
return true;
}
SE_BIND_FUNC(JSB_core_restartVM)
static bool JSB_closeWindow(se::State& s)
{
Application::getInstance()->end();
return true;
}
SE_BIND_FUNC(JSB_closeWindow)
static bool JSB_isObjectValid(se::State& s)
{
const auto& args = s.args();
int argc = (int)args.size();
if (argc == 1)
{
void* nativePtr = nullptr;
seval_to_native_ptr(args[0], &nativePtr);
s.rval().setBoolean(nativePtr != nullptr);
return true;
}
SE_REPORT_ERROR("Invalid number of arguments: %d. Expecting: 1", argc);
return false;
}
SE_BIND_FUNC(JSB_isObjectValid)
static bool getOrCreatePlainObject_r(const char* name, se::Object* parent, se::Object** outObj)
{
assert(parent != nullptr);
assert(outObj != nullptr);
se::Value tmp;
if (parent->getProperty(name, &tmp) && tmp.isObject())
{
*outObj = tmp.toObject();
(*outObj)->incRef();
}
else
{
*outObj = se::Object::createPlainObject();
parent->setProperty(name, se::Value(*outObj));
}
return true;
}
static bool js_performance_now(se::State& s)
{
auto now = std::chrono::steady_clock::now();
auto micro = std::chrono::duration_cast<std::chrono::microseconds>(now - se::ScriptEngine::getInstance()->getStartTime()).count();
s.rval().setNumber((double)micro * 0.001);
return true;
}
SE_BIND_FUNC(js_performance_now)
namespace
{
struct ImageInfo
{
~ImageInfo()
{
if (freeData)
delete [] data;
}
uint32_t length = 0;
uint32_t width = 0;
uint32_t height = 0;
uint8_t* data = nullptr;
GLenum glFormat = GL_RGBA;
GLenum glInternalFormat = GL_RGBA;
GLenum type = GL_UNSIGNED_BYTE;
uint8_t bpp = 0;
uint8_t numberOfMipmaps = 0;
bool hasAlpha = false;
bool hasPremultipliedAlpha = false;
bool compressed = false;
bool freeData = false;
};
uint8_t* convertRGB2RGBA (uint32_t length, uint8_t* src) {
uint8_t* dst = new uint8_t[length];
for (uint32_t i = 0; i < length; i += 4) {
dst[i] = *src++;
dst[i + 1] = *src++;
dst[i + 2] = *src++;
dst[i + 3] = 255;
}
return dst;
}
uint8_t* convertIA2RGBA (uint32_t length, uint8_t* src) {
uint8_t* dst = new uint8_t[length];
for (uint32_t i = 0; i < length; i += 4) {
dst[i] = *src;
dst[i + 1] = *src;
dst[i + 2] = *src++;
dst[i + 3] = *src++;
}
return dst;
}
uint8_t* convertI2RGBA (uint32_t length, uint8_t* src) {
uint8_t* dst = new uint8_t[length];
for (uint32_t i = 0; i < length; i += 4) {
dst[i] = *src;
dst[i + 1] = *src;
dst[i + 2] = *src++;
dst[i + 3] = 255;
}
return dst;
}
struct ImageInfo* createImageInfo(const Image* img)
{
struct ImageInfo* imgInfo = new struct ImageInfo();
imgInfo->length = (uint32_t)img->getDataLen();
imgInfo->width = img->getWidth();
imgInfo->height = img->getHeight();
imgInfo->data = img->getData();
const auto& pixelFormatInfo = img->getPixelFormatInfo();
imgInfo->glFormat = pixelFormatInfo.format;
imgInfo->glInternalFormat = pixelFormatInfo.internalFormat;
imgInfo->type = pixelFormatInfo.type;
imgInfo->bpp = img->getBitPerPixel();
imgInfo->numberOfMipmaps = img->getNumberOfMipmaps();
imgInfo->hasAlpha = img->hasAlpha();
imgInfo->hasPremultipliedAlpha = img->hasPremultipliedAlpha();
imgInfo->compressed = img->isCompressed();
// Convert to RGBA888 because standard web api will return only RGBA888.
// If not, then it may have issue in glTexSubImage. For example, engine
// will create a big texture, and update its content with small pictures.
// The big texture is RGBA888, then the small picture should be the same
// format, or it will cause 0x502 error on OpenGL ES 2.
if (!imgInfo->compressed && imgInfo->glFormat != GL_RGBA) {
imgInfo->length = img->getWidth() * img->getHeight() * 4;
uint8_t* dst = nullptr;
uint32_t length = imgInfo->length;
uint8_t* src = imgInfo->data;
switch(imgInfo->glFormat) {
case GL_LUMINANCE_ALPHA:
dst = convertIA2RGBA(length, src);
break;
case GL_ALPHA:
case GL_LUMINANCE:
dst = convertI2RGBA(length, src);
break;
case GL_RGB:
dst = convertRGB2RGBA(length, src);
break;
default:
SE_LOGE("unknown image format");
break;
}
imgInfo->data = dst;
imgInfo->hasAlpha = true;
imgInfo->bpp = 32;
imgInfo->glFormat = GL_RGBA;
imgInfo->glInternalFormat = GL_RGBA;
imgInfo->freeData = true;
}
return imgInfo;
}
}
bool jsb_global_load_image(const std::string& path, const se::Value& callbackVal) {
if (path.empty())
{
se::ValueArray seArgs;
callbackVal.toObject()->call(seArgs, nullptr);
return true;
}
std::shared_ptr<se::Value> callbackPtr = std::make_shared<se::Value>(callbackVal);
auto initImageFunc = [path, callbackPtr](const std::string& fullPath, unsigned char* imageData, int imageBytes, const std::string& errorMsg){
std::shared_ptr<uint8_t> imageDataGuard(imageData, free);
auto pool = g_threadPool;
if (!pool)
return;
pool->pushTask([=](int tid) mutable {
// NOTE: FileUtils::getInstance()->fullPathForFilename isn't a threadsafe method,
// Image::initWithImageFile will call fullPathForFilename internally which may
// cause thread race issues. Therefore, we get the full path of file before
// going into task callback.
// Be careful of invoking any Cocos2d-x interface in a sub-thread.
bool loadSucceed = false;
std::shared_ptr<Image> img(new Image(), [](Image *image) {
image->release();
});
if (!errorMsg.empty()) {
loadSucceed = false;
}
else if (fullPath.empty())
{
loadSucceed = img->initWithImageData(imageDataGuard.get(), imageBytes);
imageDataGuard = nullptr;
}
else
{
loadSucceed = img->initWithImageFile(fullPath);
}
std::shared_ptr<ImageInfo> imgInfo;
if(loadSucceed)
{
imgInfo.reset(createImageInfo(img.get()));
}
Application::getInstance()->getScheduler()->performFunctionInCocosThread([=]() mutable {
se::AutoHandleScope hs;
se::ValueArray seArgs;
se::Value dataVal;
std::vector< se::Object* > refs;
if (loadSucceed)
{
se::Object* retObj = se::Object::createPlainObject();
retObj->root();
refs.push_back(retObj);
Data data;
data.fastSet(imgInfo->data, imgInfo->length);
Data_to_seval(data, &dataVal);
data.takeBuffer();
retObj->setProperty("data", dataVal);
retObj->setProperty("width", se::Value(imgInfo->width));
retObj->setProperty("height", se::Value(imgInfo->height));
retObj->setProperty("premultiplyAlpha", se::Value(imgInfo->hasPremultipliedAlpha));
retObj->setProperty("bpp", se::Value(imgInfo->bpp));
retObj->setProperty("hasAlpha", se::Value(imgInfo->hasAlpha));
retObj->setProperty("compressed", se::Value(imgInfo->compressed));
retObj->setProperty("numberOfMipmaps", se::Value(imgInfo->numberOfMipmaps));
if (imgInfo->numberOfMipmaps > 0)
{
se::Object* mipmapArray = se::Object::createArrayObject(imgInfo->numberOfMipmaps);
mipmapArray->root();
refs.push_back(mipmapArray);
retObj->setProperty("mipmaps", se::Value(mipmapArray));
auto mipmapInfo = img->getMipmaps();
for (int i = 0; i < imgInfo->numberOfMipmaps; ++i)
{
se::Object* info = se::Object::createPlainObject();
info->root();
refs.push_back(info);
info->setProperty("offset", se::Value(mipmapInfo[i].offset));
info->setProperty("length", se::Value(mipmapInfo[i].len));
mipmapArray->setArrayElement(i, se::Value(info));
}
}
retObj->setProperty("glFormat", se::Value(imgInfo->glFormat));
retObj->setProperty("glInternalFormat", se::Value(imgInfo->glInternalFormat));
retObj->setProperty("glType", se::Value(imgInfo->type));
seArgs.push_back(se::Value(retObj));
imgInfo = nullptr;
}
else
{
SE_REPORT_ERROR("initWithImageFile: %s failed!", path.c_str());
}
if (!errorMsg.empty()) {
se::Object* retObj = se::Object::createPlainObject();
retObj->root();
refs.push_back(retObj);
retObj->setProperty("errorMsg", se::Value(errorMsg));
seArgs.push_back(se::Value(retObj));
}
callbackPtr->toObject()->call(seArgs, nullptr);
img = nullptr;
for (auto obj : refs) {
obj->unroot();
obj->decRef();
}
});
});
};
size_t pos = std::string::npos;
if (path.find("http://") == 0 || path.find("https://") == 0)
{
localDownloaderCreateTask(path, initImageFunc);
}
else if (path.find("data:") == 0 && (pos = path.find("base64,")) != std::string::npos)
{
int imageBytes = 0;
unsigned char* imageData = nullptr;
size_t dataStartPos = pos + strlen("base64,");
const char* base64Data = path.data() + dataStartPos;
size_t dataLen = path.length() - dataStartPos;
imageBytes = base64Decode((const unsigned char *)base64Data, (unsigned int)dataLen, &imageData);
if (imageBytes <= 0 || imageData == nullptr)
{
SE_REPORT_ERROR("Decode base64 image data failed!");
return false;
}
initImageFunc("", imageData, imageBytes, "");
}
else
{
std::string fullPath(FileUtils::getInstance()->fullPathForFilename(path));
if (0 == path.find("file://"))
fullPath = FileUtils::getInstance()->fullPathForFilename(path.substr(strlen("file://")));
if (fullPath.empty())
{
SE_REPORT_ERROR("File (%s) doesn't exist!", path.c_str());
return false;
}
initImageFunc(fullPath, nullptr, 0, "");
}
return true;
}
static bool js_loadImage(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc == 2) {
std::string path;
ok &= seval_to_std_string(args[0], &path);
SE_PRECONDITION2(ok, false, "js_loadImage : Error processing arguments");
se::Value callbackVal = args[1];
assert(callbackVal.isObject());
assert(callbackVal.toObject()->isFunction());
return jsb_global_load_image(path, callbackVal);
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);
return false;
}
SE_BIND_FUNC(js_loadImage)
//pixels(RGBA), width, height, fullFilePath(*.png/*.jpg)
static bool js_saveImageData(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc == 4) {
cocos2d::Data data;
ok &= seval_to_Data(args[0], &data);
uint32_t width, height;
ok &= seval_to_uint32(args[1], &width);
ok &= seval_to_uint32(args[2], &height);
std::string filePath;
ok &= seval_to_std_string(args[3], &filePath);
SE_PRECONDITION2(ok, false, "js_saveImageData : Error processing arguments");
Image* img = new Image();
img->initWithRawData(data.getBytes(), data.getSize(), width, height, 8);
// isToRGB = false, to keep alpha channel
bool ret = img->saveToFile(filePath, false);
s.rval().setBoolean(ret);
img->release();
return ret;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);
return false;
}
SE_BIND_FUNC(js_saveImageData)
static bool js_setDebugViewText(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc == 2) {
int32_t index;
ok = seval_to_int32(args[0], &index);
SE_PRECONDITION2(ok, false, "Convert arg0 index failed!");
std::string text;
ok = seval_to_std_string(args[1], &text);
SE_PRECONDITION2(ok, false, "Convert arg1 text failed!");
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
setGameInfoDebugViewTextJNI(index, text);
#endif
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);
return false;
}
SE_BIND_FUNC(js_setDebugViewText)
static bool js_openDebugView(se::State& s)
{
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
openDebugViewJNI();
#endif
return true;
}
SE_BIND_FUNC(js_openDebugView)
static bool js_disableBatchGLCommandsToNative(se::State& s)
{
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
disableBatchGLCommandsToNativeJNI();
#endif
return true;
}
SE_BIND_FUNC(js_disableBatchGLCommandsToNative)
static bool JSB_openURL(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc > 0) {
std::string url;
ok = seval_to_std_string(args[0], &url);
SE_PRECONDITION2(ok, false, "url is invalid!");
Application::getInstance()->openURL(url);
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
return false;
}
SE_BIND_FUNC(JSB_openURL)
static bool JSB_copyTextToClipboard(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc > 0) {
std::string text;
ok = seval_to_std_string(args[0], &text);
SE_PRECONDITION2(ok, false, "text is invalid!");
Application::getInstance()->copyTextToClipboard(text);
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
return false;
}
SE_BIND_FUNC(JSB_copyTextToClipboard)
static bool JSB_setPreferredFramesPerSecond(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc > 0) {
int32_t fps;
ok = seval_to_int32(args[0], &fps);
SE_PRECONDITION2(ok, false, "fps is invalid!");
Application::getInstance()->setPreferredFramesPerSecond(fps);
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
return false;
}
SE_BIND_FUNC(JSB_setPreferredFramesPerSecond)
static bool JSB_showInputBox(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
CC_UNUSED bool ok = true;
if (argc == 1)
{
bool ok;
se::Value tmp;
const auto& obj = args[0].toObject();
cocos2d::EditBox::ShowInfo showInfo;
ok = obj->getProperty("defaultValue", &tmp);
SE_PRECONDITION2(ok && tmp.isString(), false, "defaultValue is invalid!");
showInfo.defaultValue = tmp.toString();
ok = obj->getProperty("maxLength", &tmp);
SE_PRECONDITION2(ok && tmp.isNumber(), false, "maxLength is invalid!");
showInfo.maxLength = tmp.toInt32();
ok = obj->getProperty("multiple", &tmp);
SE_PRECONDITION2(ok && tmp.isBoolean(), false, "multiple is invalid!");
showInfo.isMultiline = tmp.toBoolean();
if (obj->getProperty("confirmHold", &tmp))
{
SE_PRECONDITION2(tmp.isBoolean(), false, "confirmHold is invalid!");
if (! tmp.isUndefined())
showInfo.confirmHold = tmp.toBoolean();
}
if (obj->getProperty("confirmType", &tmp))
{
SE_PRECONDITION2(tmp.isString(), false, "confirmType is invalid!");
if (!tmp.isUndefined())
showInfo.confirmType = tmp.toString();
}
if (obj->getProperty("inputType", &tmp))
{
SE_PRECONDITION2(tmp.isString(), false, "inputType is invalid!");
if (! tmp.isUndefined())
showInfo.inputType = tmp.toString();
}
if (obj->getProperty("originX", &tmp))
{
SE_PRECONDITION2(tmp.isNumber(), false, "originX is invalid!");
if (! tmp.isUndefined())
showInfo.x = tmp.toInt32();
}
if (obj->getProperty("originY", &tmp))
{
SE_PRECONDITION2(tmp.isNumber(), false, "originY is invalid!");
if (! tmp.isUndefined())
showInfo.y = tmp.toInt32();
}
if (obj->getProperty("width", &tmp))
{
SE_PRECONDITION2(tmp.isNumber(), false, "width is invalid!");
if (! tmp.isUndefined())
showInfo.width = tmp.toInt32();
}
if (obj->getProperty("height", &tmp))
{
SE_PRECONDITION2(tmp.isNumber(), false, "height is invalid!");
if (! tmp.isUndefined())
showInfo.height = tmp.toInt32();
}
EditBox::show(showInfo);
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
return false;
}
SE_BIND_FUNC(JSB_showInputBox);
static bool JSB_updateInputBoxRect(se::State& s)
{
const auto& args = s.args();
size_t argc = args.size();
if (argc == 4)
{
SE_PRECONDITION2(args[0].isNumber(), false, "x is invalid!");
const auto& x = args[0].toInt32();
SE_PRECONDITION2(args[1].isNumber(), false, "y is invalid!");
const auto& y = args[1].toInt32();
SE_PRECONDITION2(args[2].isNumber(), false, "width is invalid!");
const auto& width = args[2].toInt32();
SE_PRECONDITION2(args[3].isNumber(), false, "height is invalid!");
const auto& height = args[3].toInt32();
EditBox::updateRect(x, y, width, height);
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4);
return false;
}
SE_BIND_FUNC(JSB_updateInputBoxRect);
static bool JSB_hideInputBox(se::State& s)
{
EditBox::hide();
return true;
}
SE_BIND_FUNC(JSB_hideInputBox)
bool jsb_register_global_variables(se::Object* global)
{
g_threadPool.reset(ThreadPool::newFixedThreadPool(3));
#if (CC_TARGET_PLATFORM != CC_PLATFORM_OPENHARMONY)
global->defineFunction("require", _SE(require));
global->defineFunction("requireModule", _SE(moduleRequire));
#else
global->defineFunction("run_script", _SE(run_script));
#endif
2022-06-25 00:23:03 +08:00
getOrCreatePlainObject_r("jsb", global, &__jsbObj);
auto glContextCls = se::Class::create("WebGLRenderingContext", global, nullptr, nullptr);
glContextCls->install();
SAFE_DEC_REF(__glObj);
__glObj = se::Object::createObjectWithClass(glContextCls);
global->setProperty("__gl", se::Value(__glObj));
__jsbObj->defineFunction("garbageCollect", _SE(jsc_garbageCollect));
__jsbObj->defineFunction("dumpNativePtrToSeObjectMap", _SE(jsc_dumpNativePtrToSeObjectMap));
__jsbObj->defineFunction("loadImage", _SE(js_loadImage));
__jsbObj->defineFunction("saveImageData", _SE(js_saveImageData));
__jsbObj->defineFunction("setDebugViewText", _SE(js_setDebugViewText));
__jsbObj->defineFunction("openDebugView", _SE(js_openDebugView));
__jsbObj->defineFunction("disableBatchGLCommandsToNative", _SE(js_disableBatchGLCommandsToNative));
__jsbObj->defineFunction("openURL", _SE(JSB_openURL));
__jsbObj->defineFunction("copyTextToClipboard", _SE(JSB_copyTextToClipboard));
__jsbObj->defineFunction("setPreferredFramesPerSecond", _SE(JSB_setPreferredFramesPerSecond));
__jsbObj->defineFunction("showInputBox", _SE(JSB_showInputBox));
__jsbObj->defineFunction("hideInputBox", _SE(JSB_hideInputBox));
__jsbObj->defineFunction("updateInputBoxRect", _SE(JSB_updateInputBoxRect));
global->defineFunction("__getPlatform", _SE(JSBCore_platform));
global->defineFunction("__getOS", _SE(JSBCore_os));
global->defineFunction("__getOSVersion", _SE(JSB_getOSVersion));
global->defineFunction("__getCurrentLanguage", _SE(JSBCore_getCurrentLanguage));
global->defineFunction("__getCurrentLanguageCode", _SE(JSBCore_getCurrentLanguageCode));
global->defineFunction("__getVersion", _SE(JSBCore_version));
global->defineFunction("__restartVM", _SE(JSB_core_restartVM));
global->defineFunction("__cleanScript", _SE(JSB_cleanScript));
global->defineFunction("__isObjectValid", _SE(JSB_isObjectValid));
global->defineFunction("close", _SE(JSB_closeWindow));
se::HandleObject performanceObj(se::Object::createPlainObject());
performanceObj->defineFunction("now", _SE(js_performance_now));
global->setProperty("performance", se::Value(performanceObj));
se::ScriptEngine::getInstance()->clearException();
se::ScriptEngine::getInstance()->addBeforeCleanupHook([](){
g_threadPool = nullptr;
PoolManager::getInstance()->getCurrentPool()->clear();
});
se::ScriptEngine::getInstance()->addAfterCleanupHook([](){
PoolManager::getInstance()->getCurrentPool()->clear();
__moduleCache.clear();
SAFE_DEC_REF(__jsbObj);
SAFE_DEC_REF(__glObj);
});
return true;
}