ScriptEngine 类分析
文章目录
- ScriptEngine 类分析
- 1 概述
- 2 ScriptEngine 类的实现分析(v8)
- 2.1 构造和析构函数分析
- 2.2 初始化和清理
- 2.2.1 init方法
- 1)初始化准备阶段
- 2) Isolate 和 Context 创建
- 3)postInit 后续初始化
- 2.2.2 postInit 方法详解
- 1) 设置 V8 错误处理器
- 2)初始化对象映射系统
- 3)创建全局对象
- 4) 重写 console 方法
- 5)注册全局函数
- 6)触发初始化完成事件
- 2.2.3 cleanup 方法
- 1) 有效性检查与状态设置
- 2)触发清理前事件
- 3)核心资源清理
- 4)释放 Isolate 和重置状态
- 5)触发清理后事件和最终清理
- 2.2.4 脚本执行方法
- 2.2.5 字节码相关方法
- 2.2.6 调试与诊断方法
- 2.2.7 辅助工具类
- 2.2.8 文件操作委托
- 2.2.9 事件处理
- Promise 拒绝回调
- 2.2.10 日志回调
- 3.总结
1 概述
ScriptEngine 类是 Cocos Creator 引擎中用于管理 JavaScript 脚本执行的核心组件。它负责初始化脚本环境、执行脚本文件、处理异常、管理垃圾回收等关键功能。
ScriptEngine 类有多个实现版本,分别对应不同的 JavaScript 引擎
#define SCRIPT_ENGINE_NONE 0
#define SCRIPT_ENGINE_SM 1
#define SCRIPT_ENGINE_V8 2
#define SCRIPT_ENGINE_NAPI 5
#define SCRIPT_ENGINE_JSVM 6
#ifndef SCRIPT_ENGINE_TYPE
#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY
#define SCRIPT_ENGINE_TYPE SCRIPT_ENGINE_NAPI
#else
#define SCRIPT_ENGINE_TYPE SCRIPT_ENGINE_V8
#endif
#endif
V8 版本: native/cocos/bindings/jswrapper/v8/ScriptEngine.cpp - 用于大多数平台
JSVM 版本: native/cocos/bindings/jswrapper/jsvm/ScriptEngine.cpp - 用于 OpenHarmony 平台
NAPI 版本: native/cocos/bindings/jswrapper/napi/ScriptEngine.cpp - 另一个 OpenHarmony 实现
SpiderMonkey 版本: native/cocos/bindings/jswrapper/sm/ScriptEngine.cpp - 备用引擎
config.h:32-44
2 ScriptEngine 类的实现分析(v8)
2.1 构造和析构函数分析
2.1.1 构造函数
ScriptEngine::ScriptEngine() {
#if !CC_EDITOR
if (!gSharedV8) {
gSharedV8 = ccnew ScriptEngineV8Context();
}
#endif
ScriptEngine::instance = this;
}
构造函数非常简单,只进行基本的初始化。它不执行任何复杂的初始化操作,将初始化工作推迟到 init() 方法中进行。
ScriptEngine.cpp:31:60
2.1.2 析构函数
ScriptEngine::~ScriptEngine() {
cleanup();
gSriptEngineInstance = nullptr;
};
析构函数是默认实现,不执行任何特殊操作。实际的清理工作在 cleanup() 方法中完成。
ScriptEngine.cpp:31-60
2.2 初始化和清理
2.2.1 init方法
bool ScriptEngine::init(v8::Isolate *isolate) {
cleanup();
SE_LOGD("Initializing V8, version: %s\n", v8::V8::GetVersion());
++_vmId;
_engineThreadId = std::this_thread::get_id();
cc::events::ScriptEngine::broadcast(cc::ScriptEngineEvent::BEFORE_INIT);
for (const auto &hook : _beforeInitHookArray) {
hook();
}
_beforeInitHookArray.clear();
if (isolate != nullptr) {
_isolate = isolate;
v8::Local<v8::Context> const context = _isolate->GetCurrentContext();
_context.Reset(_isolate, context);
} else {
static v8::ArrayBuffer::Allocator *arrayBufferAllocator{nullptr};
if (arrayBufferAllocator == nullptr) {
arrayBufferAllocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
}
v8::Isolate::CreateParams createParams;
createParams.array_buffer_allocator = arrayBufferAllocator;
_isolate = v8::Isolate::New(createParams);
v8::HandleScope const hs(_isolate);
_context.Reset(_isolate, v8::Context::New(_isolate));
_context.Get(_isolate)->Enter();
}
return postInit();
}
1)初始化准备阶段
方法首先调用 cleanup() 清理之前的状态,然后递增 _vmId 计数器,记录当前线程 ID ScriptEngine.cpp:434-438 。
接着触发 BEFORE_INIT 事件并执行所有注册的 beforeInit 钩子函数 ScriptEngine.cpp:440-445 。
2) Isolate 和 Context 创建
方法根据是否传入外部 isolate 参数采取不同策略 ScriptEngine.cpp:447-462 :
情况 A: 使用外部 Isolate (Editor 模式)
- 直接使用传入的
isolate指针 - 获取当前 Context 并保存到
_context成员变量
情况 B: 创建新 Isolate (运行时模式)
- 创建
v8::ArrayBuffer::Allocator(仅首次创建,后续复用) - 配置
v8::Isolate::CreateParams并创建新的 Isolate - 创建新的 V8 Context 并进入该 Context
3)postInit 后续初始化
最后调用 postInit() 完成剩余初始化工作 ScriptEngine.cpp:464 。
2.2.2 postInit 方法详解
bool ScriptEngine::postInit() {
v8::HandleScope const hs(_isolate);
// editor has it's own isolate,no need to enter and set callback.
#if !CC_EDITOR
_isolate->Enter();
_isolate->SetCaptureStackTraceForUncaughtExceptions(true, JSB_STACK_FRAME_LIMIT, v8::StackTrace::kOverview);
_isolate->SetFatalErrorHandler(ScriptEngine::onFatalErrorCallback);
_isolate->SetOOMErrorHandler(ScriptEngine::onOOMErrorCallback);
_isolate->AddMessageListener(ScriptEngine::onMessageCallback);
_isolate->SetPromiseRejectCallback(ScriptEngine::onPromiseRejectCallback);
#endif
NativePtrToObjectMap::init();
Class::setIsolate(_isolate);
Object::setIsolate(_isolate);
_globalObj = Object::_createJSObject(nullptr, _isolate->GetCurrentContext()->Global());
_globalObj->root();
_globalObj->setProperty("window", Value(_globalObj));
#if !CC_EDITOR
se::Value consoleVal;
if (_globalObj->getProperty("console", &consoleVal) && consoleVal.isObject()) {
consoleVal.toObject()->getProperty("log", &oldConsoleLog);
consoleVal.toObject()->defineFunction("log", _SE(jsbConsoleLog));
consoleVal.toObject()->getProperty("debug", &oldConsoleDebug);
consoleVal.toObject()->defineFunction("debug", _SE(jsbConsoleDebug));
consoleVal.toObject()->getProperty("info", &oldConsoleInfo);
consoleVal.toObject()->defineFunction("info", _SE(jsbConsoleInfo));
consoleVal.toObject()->getProperty("warn", &oldConsoleWarn);
consoleVal.toObject()->defineFunction("warn", _SE(jsbConsoleWarn));
consoleVal.toObject()->getProperty("error", &oldConsoleError);
consoleVal.toObject()->defineFunction("error", _SE(jsbConsoleError));
consoleVal.toObject()->getProperty("assert", &oldConsoleAssert);
consoleVal.toObject()->defineFunction("assert", _SE(jsbConsoleAssert));
}
#endif
_globalObj->setProperty("scriptEngineType", se::Value("V8"));
_globalObj->defineFunction("log", seLogCallback);
_globalObj->defineFunction("forceGC", seForceGC);
_globalObj->getProperty(EXPOSE_GC, &_gcFuncValue);
if (_gcFuncValue.isObject() && _gcFuncValue.toObject()->isFunction()) {
_gcFunc = _gcFuncValue.toObject();
} else {
_gcFunc = nullptr;
}
_isValid = true;
// @deprecated since 3.7.0
cc::plugin::send(cc::plugin::BusType::SCRIPT_ENGINE, cc::plugin::ScriptEngineEvent::POST_INIT);
cc::events::ScriptEngine::broadcast(cc::ScriptEngineEvent::AFTER_INIT);
for (const auto &hook : _afterInitHookArray) {
hook();
}
_afterInitHookArray.clear();
return _isValid;
}
postInit 方法执行以下关键步骤 ScriptEngine.cpp:360-427 :
1) 设置 V8 错误处理器
在非 Editor 模式下,配置 V8 的各种错误回调 ScriptEngine.cpp:363-370 :
SetCaptureStackTraceForUncaughtExceptions: 捕获未处理异常的堆栈信息SetFatalErrorHandler: 致命错误处理器SetOOMErrorHandler: 内存溢出错误处理器AddMessageListener: 消息监听器SetPromiseRejectCallback: Promise 拒绝回调
2)初始化对象映射系统
初始化 Native 指针到 JS 对象的映射表,并设置 Class 和 Object 的 Isolate ScriptEngine.cpp:371-374 。
3)创建全局对象
创建全局 JavaScript 对象并设置 window 属性指向自身 ScriptEngine.cpp:376-378 。
4) 重写 console 方法
在非 Editor 模式下,重写 console 对象的各个方法以支持自定义日志格式化 ScriptEngine.cpp:380-401 :
log,debug,info,warn,error,assert
这些方法会调用对应的 jsbConsoleLog, jsbConsoleDebug 等函数,它们使用 jsbConsoleFormatLog 进行格式化输出 ScriptEngine.cpp:208-220 。
5)注册全局函数
设置引擎类型标识和全局工具函数 ScriptEngine.cpp:402-412 :
scriptEngineType: 设置为 “V8”log: 日志函数forceGC: 强制垃圾回收函数- 获取 V8 暴露的
__jsb_gc__函数引用
6)触发初始化完成事件
标记引擎为有效状态,触发 AFTER_INIT 事件并执行所有 afterInit 钩子 ScriptEngine.cpp:414-426 。
ScriptEngine.cpp:91-120
2.2.3 cleanup 方法
void ScriptEngine::cleanup() {
if (!_isValid) {
return;
}
SE_LOGD("ScriptEngine::cleanup begin ...\n");
_isInCleanup = true;
cc::events::ScriptEngine::broadcast(cc::ScriptEngineEvent::BEFORE_CLEANUP);
{
AutoHandleScope const hs;
for (const auto &hook : _beforeCleanupHookArray) {
hook();
}
_beforeCleanupHookArray.clear();
// Clear unhandled promises
_unhandledPromises.clear();
_ignoreUnhandledPromises = false;
_stringPool.clear();
SAFE_DEC_REF(_globalObj);
Object::cleanup();
Class::cleanup();
garbageCollect();
oldConsoleLog.setUndefined();
oldConsoleDebug.setUndefined();
oldConsoleInfo.setUndefined();
oldConsoleWarn.setUndefined();
oldConsoleError.setUndefined();
oldConsoleAssert.setUndefined();
#if SE_ENABLE_INSPECTOR
if (_env != nullptr) {
_env->inspector_agent()->Disconnect();
_env->inspector_agent()->Stop();
}
if (_isolateData != nullptr) {
node::FreeIsolateData(_isolateData);
_isolateData = nullptr;
}
if (_env != nullptr) {
_env->CleanupHandles();
node::FreeEnvironment(_env);
_env = nullptr;
}
#endif
_context.Get(_isolate)->Exit();
_context.Reset();
_isolate->Exit();
}
_isolate->Dispose();
_isolate = nullptr;
Object::setIsolate(nullptr);
_globalObj = nullptr;
_isValid = false;
_registerCallbackArray.clear();
for (const auto &hook : _afterCleanupHookArray) {
hook();
}
// Cleanup all hooks
_beforeInitHookArray.clear();
_afterInitHookArray.clear();
_beforeCleanupHookArray.clear();
_afterCleanupHookArray.clear();
_isInCleanup = false;
NativePtrToObjectMap::destroy();
_gcFuncValue.setUndefined();
_gcFunc = nullptr;
cc::events::ScriptEngine::broadcast(cc::ScriptEngineEvent::AFTER_CLEANUP);
SE_LOGD("ScriptEngine::cleanup end ...\n");
}
1) 有效性检查与状态设置
方法首先检查 _isValid 标志,如果引擎已经无效则直接返回 ScriptEngine.cpp:468-470 。然后设置 _isInCleanup 标志为 true,这个标志用于防止在清理过程中执行某些操作 ScriptEngine.cpp:472-473 。
2)触发清理前事件
触发 BEFORE_CLEANUP 事件并执行所有注册的 beforeCleanup 钩子函数 ScriptEngine.cpp:475-482 。这些钩子允许外部代码在清理开始前执行必要的清理工作,例如在 jsb_module_register.cpp 中注册的钩子会清理 DeferredReleasePool 和 JSBClassType jsb_module_register.cpp:265-268 。
3)核心资源清理
在 AutoHandleScope 作用域内执行核心清理操作 ScriptEngine.cpp:478-523 :
清理 Promise 相关状态
清空未处理的 Promise 队列并重置忽略标志 ScriptEngine.cpp:484-486 。这些 Promise 在 onPromiseRejectCallbackPerInstance 中被跟踪 ScriptEngine.cpp:1221-1248 。
清理字符串池
清空 _stringPool,释放缓存的字符串资源 ScriptEngine.cpp:488 。
清理全局对象和映射系统
按顺序清理核心组件 ScriptEngine.cpp:490-493 :
- 释放全局对象
_globalObj的引用 - 调用
Object::cleanup()清理所有 JS 对象包装器 - 调用
Class::cleanup()清理所有注册的类 - 执行垃圾回收
garbageCollect()ScriptEngine.cpp:649-653
清理 console 对象引用
重置所有保存的 console 方法引用 ScriptEngine.cpp:495-500 。这些引用在 postInit 中被保存 ScriptEngine.cpp:383-399 。
清理调试器资源 (如果启用)
如果启用了调试器支持 (SE_ENABLE_INSPECTOR),则清理调试器相关资源 ScriptEngine.cpp:502-519 :
- 断开并停止 inspector agent
- 释放 isolate data
- 清理并释放 environment
退出 V8 Context 和 Isolate
退出当前 Context 并重置,然后退出 Isolate ScriptEngine.cpp:521-523 。
4)释放 Isolate 和重置状态
在 AutoHandleScope 作用域外,释放 Isolate 并重置相关状态 ScriptEngine.cpp:525-532 :
- 调用
_isolate->Dispose()释放 Isolate - 将
_isolate设为nullptr - 重置
Object的 Isolate 引用 - 清空
_globalObj指针 - 设置
_isValid为false - 清空注册回调数组
5)触发清理后事件和最终清理
执行 afterCleanup 钩子并清理所有钩子数组 ScriptEngine.cpp:534-542 。重置 _isInCleanup 标志,销毁 Native 指针映射表,清理 GC 函数引用,最后触发 AFTER_CLEANUP 事件 ScriptEngine.cpp:544-549 。
2.2.4 脚本执行方法
runScript - 执行脚本文件
bool ScriptEngine::runScript(const ccstd::string &path, Value *ret /* = nullptr */) {
CC_ASSERT(!path.empty());
CC_ASSERT(_fileOperationDelegate.isValid());
if (!cc::FileUtils::getInstance()->isFileExist(path)) {
std::stringstream ss;
ss << "throw new Error(\"Failed to require file '"
<< path << "', not found!\");";
evalString(ss.str().c_str());
return false;
}
if (path.length() > 3 && path.substr(path.length() - 3) == ".bc") {
return runByteCodeFile(path, ret);
}
ccstd::string const scriptBuffer = _fileOperationDelegate.onGetStringFromFile(path);
if (!scriptBuffer.empty()) {
return evalString(scriptBuffer.c_str(), static_cast<uint32_t>(scriptBuffer.length()), ret, path.c_str());
}
SE_LOGE("ScriptEngine::runScript script %s, buffer is empty!\n", path.c_str());
return false;
}
该方法从文件系统读取脚本并执行: ScriptEngine.cpp:919-943
evalString - 执行字符串代码
bool ScriptEngine::evalString(const char *script, uint32_t length /* = 0 */, Value *ret /* = nullptr */, const char *fileName /* = nullptr */) {
if (_engineThreadId != std::this_thread::get_id()) {
// `evalString` should run in main thread
CC_ABORT();
return false;
}
CC_ASSERT_NOT_NULL(script);
if (length == 0) {
length = static_cast<uint32_t>(strlen(script));
}
if (fileName == nullptr) {
fileName = "(no filename)";
}
v8::TryCatch const tryCatch(_isolate);
// Fix the source url is too long displayed in Chrome debugger.
ccstd::string sourceUrl = fileName;
static const ccstd::string PREFIX_KEY = "/temp/quick-scripts/";
size_t const prefixPos = sourceUrl.find(PREFIX_KEY);
if (prefixPos != ccstd::string::npos) {
sourceUrl = sourceUrl.substr(prefixPos + PREFIX_KEY.length());
}
// It is needed, or will crash if invoked from non C++ context, such as invoked from objective-c context(for example, handler of UIKit).
v8::HandleScope const handleScope(_isolate);
ccstd::string const scriptStr(script, length);
v8::MaybeLocal<v8::String> source = v8::String::NewFromUtf8(_isolate, scriptStr.c_str(), v8::NewStringType::kNormal);
if (source.IsEmpty()) {
return false;
}
v8::MaybeLocal<v8::String> originStr = v8::String::NewFromUtf8(_isolate, sourceUrl.c_str(), v8::NewStringType::kNormal);
if (originStr.IsEmpty()) {
return false;
}
v8::ScriptOrigin origin(_isolate, originStr.ToLocalChecked());
v8::MaybeLocal<v8::Script> maybeScript = v8::Script::Compile(_context.Get(_isolate), source.ToLocalChecked(), &origin);
bool success = false;
if (maybeScript.IsEmpty()) {
reportException(_isolate, tryCatch);
} else {
v8::Local<v8::Script> const v8Script = maybeScript.ToLocalChecked();
v8::MaybeLocal<v8::Value> maybeResult = v8Script->Run(_context.Get(_isolate));
if (!maybeResult.IsEmpty()) {
v8::Local<v8::Value> const result = maybeResult.ToLocalChecked();
if (!result->IsUndefined() && ret != nullptr) {
internal::jsToSeValue(_isolate, result, ret);
}
success = true;
}
if (tryCatch.HasCaught()) {
SE_LOGE("ScriptEngine::evalString catch exception:\n");
reportException(_isolate, tryCatch);
}
}
if (!success) {
SE_LOGE("ScriptEngine::evalString script %s, failed!\n", fileName);
}
return success;
}
直接执行 JavaScript 代码字符串: ScriptEngine.cpp:672-743。
2.2.5 字节码相关方法
saveByteCodeToFile - 保存字节码
V8 版本支持将脚本编译为字节码并保存: ScriptEngine.cpp:763-831
关键步骤:
- 验证输出路径以
.bc结尾 - 创建目录结构
- 加载并编译脚本为
UnboundScript - 生成
CachedData并写入文件
runByteCodeFile - 执行字节码
从 .bc 文件加载并执行预编译的字节码: ScriptEngine.cpp:833-917
2.2.6 调试与诊断方法
getCurrentStackTrace - 获取调用栈
生成当前 JavaScript 执行栈的字符串表示: ScriptEngine.cpp:745-753
栈信息格式化由 stackTraceToString 辅助函数完成: ScriptEngine.cpp:90-139
enableDebugger - 启用调试器
配置调试器连接参数: ScriptEngine.cpp:452-461
2.2.7 辅助工具类
AutoHandleScope - 自动句柄作用域管理
V8 版本使用 RAII 模式管理 V8 的 HandleScope: ScriptEngine.cpp:64-75
JSVM 版本类似: ScriptEngine.cpp:43-49
2.2.8 文件操作委托
FileOperationDelegate 提供文件系统抽象接口: ScriptEngine.h:54-77
通过 setFileOperationDelegate 和 getFileOperationDelegate 方法设置和获取: ScriptEngine.cpp:155-161
2.2.9 事件处理
Promise 拒绝回调
V8 版本处理未捕获的 Promise 拒绝
2.2.10 日志回调
提供 JavaScript 日志输出到 C++ 的桥接
3.总结
ScriptEngine 类是 Cocos Creator 引擎中用于管理 JavaScript 脚本执行的核心组件。它负责初始化脚本环境、执行脚本文件、处理异常、管理垃圾回收等关键功能。主要特点包括:
- 提供初始化和清理方法,管理脚本引擎的生命周期
- 支持执行脚本文件和字符串形式的脚本代码
- 提供钩子函数,允许在初始化和清理过程中执行自定义逻辑
- 提供全局对象访问和方法调用功能
该类的设计体现了模块化和可扩展性的原则,通过钩子函数机制允许外部组件在关键阶段插入自定义逻辑。虽然部分功能如异常处理和垃圾回收尚未完全实现,但整体架构为脚本执行提供了坚实的基础。
9119

被折叠的 条评论
为什么被折叠?



