chronos-kit/chronos

GitHub: chronos-kit/chronos

一个高性能的跨平台 2D/3D 内容渲染与动画框架,支持将复杂的视觉效果和互动游戏集成到移动端和 Web 应用中。

Stars: 37 | Forks: 3

Chronos animated logo

案例展示 | 入门指南 | 高级功能 | Package Creator | 讨论 | 问题

_Chronos_ 是一个功能丰富的框架,旨在将高性能的 2D / 3D 内容和流畅的动画集成到您的应用中,或者利用一整套基于 2D / 3D 的游戏工具来创建游戏。 _Chronos Engine_ 是一个可以集成到您应用中的引擎。它仅负责加载和运行 _Chronos Package_。您独特的业务逻辑应该封装在 _Chronos Package_ 中,该包可以使用 _Chronos Package Creator_ 创建。 有关 _Chronos Package Creator_ 的更多信息,请参阅 _Chronos Package Creator_ 项目中的 [chronos-pkg](https://github.com/chronos-kit/chronos-pkg)。 ## 案例展示 使用 _Chronos Engine_ 构建的 Demo: _Chronos_ 渲染引擎已部署在 Bilibili 移动端的弹幕(评论)场景中,提供了丰富的弹幕效果,从而丰富了社区互动——如下面两个 Demo 所示。 ### Particle Absorb — 粒子吸收视觉效果 https://github.com/user-attachments/assets/10889910-d5a8-4cb8-921c-93d4d6f319ef ### Firework — 烟花粒子效果 https://github.com/user-attachments/assets/34575bc2-9038-4652-9db7-f2c2c1ec5fc5 除了弹幕之外,_Chronos_ 还为 Bilibili 跨年晚会上的互动节奏游戏提供了支持。 ### Music Game — 基于节拍的节奏游戏 https://github.com/user-attachments/assets/ed4a57bc-a8c0-4b93-b0d6-ba1969878afa 个人用户也可以使用 _Chronos_ 构建自己的小游戏。 ### Watermelon — 合成益智游戏 https://github.com/user-attachments/assets/8f057f8e-ab4c-478c-a6a9-010ef1bd9840 ## 入门指南 本教程将指导您完成将 _Chronos Engine_ 集成到您应用中的过程。有关如何创建 _Chronos Package_ 的说明,请参阅 _Chronos Package Creator_ 项目中的 [chronos-pkg](https://github.com/chronos-kit/chronos-pkg)。 ### 前置条件 _Chronos Engine_ 在所有支持的平台上均使用 _CMake_ 作为其构建系统。请确保您的开发环境中包含 _CMake_。 要为 _iOS_ 或 _macOS_ 构建 _Chronos Engine_,您需要安装 _Xcode_。对于 _Android_,需要 _Android SDK_ 和 _Android NDK_。对于 _Web_(_WebAssembly_),需要 _Emscripten_。对于 _Windows (Win32)_,需要 _Microsoft Visual Studio_。 #### CMake _CMake_ 是一个跨平台的构建系统,或者更准确地说是一个构建配置器。它是一个程序,通过一组 _CMake_ 脚本,为您的平台创建一个原生构建系统,使您能够构建 _Chronos Engine_。 _Chronos Engine_ 要求 _CMake_ 的版本为 3.15.0 或更高才能作为其构建工具。如果您要为 _iOS_ 或 _macOS_ 构建 _Chronos Engine_,则需要 _CMake_ 版本为 3.20.0 或更高。 - 要下载 _CMake_,请访问 [cmake.org](https://cmake.org)。 - 要检查您当前的 _CMake_ 版本,请在终端/控制台窗口中运行 `cmake --version`。 #### Xcode 请注意,_Chronos Engine_ 的 _iOS_ 或 _macOS_ 版本只能在 _macOS_ 上构建。 #### Android SDK 和 Android NDK 最新的 _Android SDK_ 和 _Android NDK_ 可以从 _Android Studio_ 的 _SDK Manager_ 或 _sdkmanager_ 命令行工具获取。 #### Emscripten _Emscripten_ 是一个完整的 WebAssembly 编译器工具链,使用 _LLVM_,特别关注速度、大小和 _Web_ 平台。 _Chronos Engine_ 要求 _Emscripten_ 的版本为 4.0.6 或更高才能作为其构建工具。 #### Visual Studio _Chronos Engine_ 要求 _Visual Studio 2022_ 的版本为 17.0 或更高才能作为其构建工具。 ### 在 iOS (macOS) 应用中使用 #### 第 1 步:为 iOS (macOS) 构建 Chronos Engine 要为 _iOS_ 构建 _Chronos Engine_ framework,请在终端窗口中执行以下命令: ``` # 确保您当前的工作目录是项目根目录 cd chronos # 为 iOS 生成 Xcode 项目 cmake . -B build-ios -G Xcode -DCMAKE_TOOLCHAIN_FILE=CMake/ios.toolchain.cmake -DPLATFORM=OS64COMBINED # 为 iOS 构建 Chronos 框架 cmake --build ./build-ios --target chronos --config Release ``` 要为 _macOS_ 构建 _Chronos Engine_ framework,请在终端窗口中执行以下命令: ``` # 确保您当前的工作目录是项目根目录 cd chronos # 为 macOS 生成 Xcode 项目 cmake . -B build-macos -G Xcode # 为 macOS 构建 Chronos 框架 cmake --build ./build-macos --target chronos --config Release ``` 执行构建命令后,构建好的 framework `Chronos.framework` 的路径将显示在您的终端窗口中。 #### 第 2 步:将 Chronos Engine Framework 嵌入到您的 iOS (macOS) 项目中 要使用 _Chronos Engine_,您需要将其 framework 嵌入到您的应用中: 1. 打开您应用的 _Xcode_ 项目或工作区。 2. 导航到应用 target 的 `General` 配置页面。 3. 通过点击 `Add` 按钮,将 framework target 添加到 `Embedded Binaries` 部分。 4. 从可以嵌入的二进制文件列表中选择您的 framework。 #### 第 3 步:在您的 iOS (macOS) 应用中运行 Chronos Package `CRONView` 是一个 `UIView`(对于 _iOS_)或 `NSView`(对于 _macOS_)的子类,用于运行 _Chronos Package_。在运行 _Chronos Package_ 之前,您需要将 `CRONView` 添加到您应用的视图树中。这可以通过编程方式完成,也可以使用 _Xcode_ 的内置工具 [Interface Builder](https://developer.apple.com/xcode/interface-builder/) 完成。 接下来,加载一个 _Chronos Package_。使用指定 _Chronos Package_ 文件的内容创建一个 `CRONPackage` 对象。该文件可以是打包在您应用中的本地 _Chronos Package_ 文件,也可以是从网络下载的 _Chronos Package_ 文件。 最后,调用 `CRONView` 的 `runPackage:completionHandler:` 方法来运行该 package。 以下是 _iOS_ 应用程序的一个示例: ``` #import "ViewController.h" // Import headers of Chronos #import @interface ViewController () // Store the CRONView as a property of your View Controller for subsequent use @property(strong, nonatomic) CRONView* cronView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Create a new CRONView, and initialize its frame with the parent view's bounds self.cronView = [[CRONView alloc] initWithFrame:self.view.bounds]; // Set the autoresizing mask for the CRONView to ensure that the CRONView // resizes with the parent view's size self.cronView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); // Add the CRONView to the app's view tree [self.view addSubview:self.cronView]; // Locate a local Chronos Package file which has been bundled into the app NSString* path = [NSBundle.mainBundle pathForResource:@"demo" ofType:@"cron"]; // Create a CRONPackage by the contents of the Chronos Package file CRONPackage* cronPackage = [[CRONPackage alloc] initWithContentsOfFile:path]; // Run the package [self.cronView runPackage:cronPackage completionHandler:nil]; } @end ``` 您可以使用类似的代码在 _macOS_ 应用程序中运行 _Chronos Package_。 ### 在 Android 应用中使用 #### 第 1 步:为 Android 构建 Chronos Engine Android 项目需要 _Gradle_ 进行构建。 要将 _Chronos Engine_ 构建为 _Android Archive_ (AAR) 文件,请在终端/控制台窗口中执行以下命令: ``` # 确保您当前的工作目录是项目根目录 cd chronos # 构建 Chronos AAR ./gradlew :chronos:assembleRelease ``` 执行构建命令后,您可以在 `build-android/library/outputs/aar` 目录中找到 _AAR_ 文件。 注意:_JavaDoc_ 不能包含在 _AAR_ 文件中。如果您需要 _JavaDoc_,可以在终端/控制台窗口中使用以下命令生成它: ``` # 确保您当前的工作目录是项目根目录 cd chronos # 生成 Chronos Engine JavaDoc HTML 文件 ./gradlew :chronos:javadoc # 生成 Chronos Engine JavaDoc jar-file ./gradlew :chronos:javadocJar ``` 执行命令后,您可以在 `build-android/library/docs/javadoc` 目录中找到 _JavaDoc_ 的 _HTML_ 文件,并在 `build-android/library/libs` 目录中找到 _JavaDoc_ 的 _jar-file_。 #### 第 2 步:将 Chronos Engine 集成到您的 Android 项目中 在 _Android Studio_ 中打开您应用的项目,并将 _Chronos Engine_ 的 _AAR_ 文件添加为依赖项。有关详细步骤,请参阅 Android Studio 的 [官方文档](https://developer.android.com/studio/projects/android-library)。 注意:Android 版本的 _Chronos Engine_ 依赖于 `androidx.annotation`,您的应用需要显式包含它以避免链接错误。 #### 第 3 步:在您的 Android 应用中运行 Chronos Package `ChronosView` 是一个可以运行 _Chronos Package_ 的 `View` 子类。在运行 _Chronos Package_ 之前,您需要将 `ChronosView` 添加到您应用的视图树中。这可以通过编程方式完成,也可以使用 _Android Studio_ 的内置工具 [Layout Editor](https://developer.android.com/studio/write/layout-editor) 完成。 接下来,加载一个 _Chronos Package_。您可以从指定 _Chronos Package_ 文件的内容中创建一个 `ChronosPackage` 对象。该文件可以是打包在您应用中的本地 _Chronos Package_ 文件,也可以是从网络下载的 _Chronos Package_ 文件。 最后,调用 `ChronosView` 的 `runPackage` 方法来运行该 package。 注意:我们强烈建议在不再使用 `ChronosView` 实例时显式调用其 `release` 方法。 以下是一个示例: ``` import android.app.Activity; import android.os.Bundle; import android.view.ViewGroup; import android.widget.FrameLayout; import com.bilibili.cron.ChronosPackage; import com.bilibili.cron.ChronosView; import java.io.File; public class YourActivity extends Activity { ChronosView chronosView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FrameLayout container = new FrameLayout(this); setContentView(container); // Create a new ChronosView, and add it to your container view chronosView = new ChronosView(this); container.addView(chronosView, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); // Create a Chronos Package from a local file File packageFile = new File("/sdcard/demo.cron"); ChronosPackage chronosPackage = ChronosPackage.createPackageFromFile(this, packageFile); // Run the package chronosView.runPackage(chronosPackage, null); } @Override protected void onDestroy() { // Release the ChronosView chronosView.release(); super.onDestroy(); } } ``` ### 在 Web 应用中使用 #### 第 1 步:为 Web 构建 Chronos Engine 要为 _Web_(_WebAssembly_)构建 _Chronos Engine_,请在终端/控制台窗口中使用以下命令: ``` # 确保您当前的工作目录是项目根目录 cd chronos # 安装依赖项;postinstall hook 会为 Web 构建 Chronos Engine npm install ``` 第一次运行 `npm install` 会触发一个自动编译引擎的 `postinstall` 钩子。在更改了引擎或 JavaScript 源代码后,请使用以下命令重新构建: ``` # Release 构建(默认) npm run build # Debug 构建(断言,内联 source maps) npm run build:debug ``` 执行构建命令后,您可以在 `build-web/dist` 目录中找到输出文件: - `chronos.d.ts`:_Chronos Engine_ _JavaScript_ API 的类型定义。 - `chronos.umd.min.js`:以 _UMD_ 格式导出的 _Chronos Engine_ _JavaScript_ 模块。 - `chronos.esm.min.js`:以 _ES Module_ 格式导出的 _Chronos Engine_ _JavaScript_ 模块。 - `chronos.wasm`:由 _Chronos Engine_ 核心 C++ 代码编译而成的 _WebAssembly_ 二进制文件。该文件是在 _Web_ 上运行 _Chronos Engine_ 所必需的,它将在运行时被 `chronos.umd.min.js` 或 `chronos.esm.min.js` 加载。 #### 第 2 步:部署 Chronos Engine 编译文件 _Chronos Engine_ 模块可以使用 _UMD_ 或 _ES Module_ 格式进行集成。建议将 _JavaScript_ 文件(_UMD_ 对应 `chronos.umd.min.js`,_ES Module_ 对应 `chronos.esm.min.js`)和 _WebAssembly_ 文件(`chronos.wasm`)直接部署在您网站的 CDN 上。 在托管这些 _JavaScript_ 和 _WebAssembly_ 文件时,确保所有内容都使用 gzip 压缩传输至关重要,目前所有浏览器和 CDN 都内置了对 gzip 压缩的支持。与未压缩的文件相比,使用 Gzip 压缩 `.wasm` 文件平均可减小 60-75% 的体积,因此在实际应用中,提供未压缩的文件是毫无意义的。 要在 CDN 上提供 gzip 压缩的资源,请使用 gzip 压缩工具并在上传到 CDN 之前离线预压缩资源文件。一些 Web 服务器支持即时压缩文件,但对于静态资源内容,应避免这样做,因为不断重新压缩文件会对服务器 CPU 造成巨大开销。调整 Web 服务器的配置,使其在托管预压缩文件时带有 HTTP 响应头 `Content-Encoding: gzip`。这会指示 Web 浏览器在将数据移交给页面本身之前,透明地对下载的内容进行解压。 确保 gzip 压缩不会混淆提供资源时所用的 MIME 类型。所有 _JavaScript_ 文件(无论是否经过预压缩)最好都使用 HTTP 响应头 `Content-Type: application/javascript` 来提供,而 _WebAssembly_ 文件应使用 `Content-Type: application/wasm` 来提供。 Web 平台上的 _Chronos Engine_ 需要浏览器具备 `SharedArrayBuffer` 的能力。由于限制使用 `SharedArrayBuffer` 的浏览器安全限制,使用 _Chronos Engine_ 的页面需要添加 `Cross-Origin Opener Policy` (COOP) 和 `Cross-Origin Embedder Policy` (COEP) 标头。有关更多信息,请参见[这里](https://web.dev/coop-coep)。 最后,请注意 HTTP 的 `Cross-Origin Resource Sharing` (CORS) 规则以及它们如何与您所托管的网站架构相关联。有关更多信息,请参见[这里](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)。 有关服务器上的 HTTP 响应头设置,请参阅 `example/wasm/server.cjs`。 #### 第 3 步:将 Chronos Engine 集成到您的项目中 如果您倾向于将 _Chronos Engine_ 作为 _UMD_ 模块导入,您可以使用 [RequireJS](https://requirejs.org) 或直接使用 ` ``` 导入后,您可以在 `chronos` 对象中找到 _Chronos Engine_ 的各个类。 如果您倾向于将 _Chronos Engine_ 作为 _ES Module_ 导,您可以直接在 _JavaScript_ 代码中使用 `import` 语句: ``` import { ChronosView } from "https://your-cdn.com/chronos.esm.min.js"; const chronosView = await ChronosView.create(); // Use the Chronos Engine ... ``` #### 第 4 步:在您的 Web 应用中运行 Chronos Package 您可以使用 `chronos.ChronosView.create()` 方法创建一个 `ChronosView` 实例。`ChronosView` 会自动构建一个 `HTMLCanvasElement`,您可以通过 `canvas` 属性访问它。将此 canvas 添加到您应用的 DOM 树中,_Chronos Engine_ 就会将内容渲染到它上面。最后,调用 `runPackage` 方法来运行一个 package。 ``` import { ChronosView } from "https://your-cdn.com/chronos.esm.min.js"; // Initialize the Chronos Engine let chronosView; try { chronosView = await ChronosView.create(); } catch (e) { console.error("Failed to create ChronosView:", e); } if (chronosView) { // Maintain a reference to the chronos view to prevent it from being collected window.chronosView = chronosView; // Add the canvas inside the chronos view on the DOM tree document.body.appendChild(chronosView.canvas); // Run your package await chronosView.runPackage("https://your-cdn.com/your-package.cron"); } ``` ### 在 Windows (Win32) 应用中使用 #### 第 1 步:为 Windows (Win32) 构建 Chronos Engine 要为 _Win32_ 构建 _Chronos Engine_ 库,请在终端窗口中执行以下命令: ``` # 确保您当前的工作目录是项目根目录 cd chronos # 为 Win32 (x64) 生成 Visual Studio 项目 cmake . -B build-win32 -G "Visual Studio 17 2022" -A x64 # 为 Win32 构建 Chronos 库 cmake --build ./build-win32 --target chronos --config Release # 将 Chronos 库安装到特定目录 cmake --install ./build-win32 --config Release --prefix install-win32 ``` 您可以将 `install-win32` 替换为自定义的安装路径。安装后,您会在 `include` 目录中找到 Chronos 的公共头文件,并在 `lib` 和 `bin` 目录中分别找到 _DLL 导入库_ 和 _DLL_ 文件。此外,在 `cmake` 目录中,您还会发现几个用于将 _Chronos Engine_ 集成到其他 _CMake_ 项目中的 _CMake_ 配置文件。 #### 第 2 步:将 Chronos Engine 集成到您的 Windows (Win32) 项目中 1. 将包含 Chronos 公共头文件(`include`)的目录添加到您项目的 include 搜索路径中。 2. 将包含 Chronos 的 _DLL 导入库_(`lib`)的目录添加到您项目的库搜索路径中。 3. 将 Chronos 的 _DLL_ 文件从 `bin` 目录复制到与您的最终产品(.exe)相同的目录中,以便进行部署。 如果您的项目也使用了 _CMake_,您可以轻松地利用 _CMake_ 的 `find_package` 功能来集成 _Chronos Engine_,从而无需担心上述细节。 以下是如何在您的 CMakeLists.txt 中包含 Chronos 的示例: ``` cmake_minimum_required(VERSION 3.15) # 设置项目名称和版本 project(MyProject VERSION 1.0) # 强制启用 Unicode 字符集 add_definitions(-D_UNICODE -DUNICODE) # 将 CMake 模块搜索路径设置为上一步安装的 cmake 目录 set(CMAKE_PREFIX_PATH "C:/Developer/project/chronos/chronos/install-win32/cmake") # 查找 Chronos 库 find_package(chronos REQUIRED) # 创建可执行文件 add_executable(MyApp WIN32 main.cpp) # 将 Chronos 库链接到项目 target_link_libraries(MyApp chronos) # 将依赖的 DLL 文件复制到可执行文件目录 add_custom_command(TARGET MyApp POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $ COMMAND_EXPAND_LISTS ) ``` #### 第 3 步:在您的 Windows (Win32) 应用中运行 Chronos Package 您可以使用 `CRONViewCreateWindow` 函数创建一个由 _Chronos Engine_ 渲染的窗口。之后,您可以使用 `CRONViewFromWindow` 函数从创建的窗口中获取一个 `CRONView` 实例(_Chronos Engine_ 的句柄)。通过此实例,您可以运行 _Chronos package_ 并与 Chronos 脚本环境进行通信。 您不需要显式释放 `CRONView` 实例。它的生命周期与它所关联的窗口绑定在一起。一旦窗口被销毁,与其相关的 _Chronos Engine_ 资源也将被释放。 这是一个简单的示例: ``` #include // Include the Chronos header #include LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, PWSTR lp_cmd_line, int cmd_show) { WNDCLASSEXW wc{}; wc.cbSize = sizeof(WNDCLASSEXW); wc.style = CS_CLASSDC; wc.lpfnWndProc = WindowProcedure; wc.cbClsExtra = 0L; wc.cbWndExtra = 0L; wc.hInstance = GetModuleHandle(NULL); wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = L"SimpleWin32App"; wc.hIconSm = NULL; RegisterClassExW(&wc); HWND hwnd = CreateWindowW(wc.lpszClassName, L"Demo", WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_SIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 500, 400, NULL, NULL, wc.hInstance, NULL); RECT client_rect; GetClientRect(hwnd, &client_rect); // Create a Chronos window HWND cron_hwnd = CRONViewCreateWindow( hwnd, // Parent window true, // Whether the window is opaque client_rect.left, // Window position x coordinate client_rect.top, // Window position y coordinate client_rect.right - client_rect.left, // Window width client_rect.bottom - client_rect.top, // Window height nullptr, // Direct3D device for Chronos engine (nullable) nullptr // External logger (nullable) ); // Get the Chronos engine object CRONView cron_view = CRONViewFromWindow(cron_hwnd); // Create a Chronos package object from a local file path CRONPackage cron_package = CRONPackageCreate(L"C:\\path\\to\\demo.cron"); // Execute the Chronos package object CRONPackageRunnerRunPackage( cron_view, // Chronos view (package runner) cron_package, // Chronos package to run nullptr, // Custom script (nullable) nullptr // Callback function after execution (nullable) ); ShowWindow(hwnd, SW_SHOWDEFAULT); UpdateWindow(hwnd); MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageW(&msg); } return static_cast(msg.wParam); } LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProcW(hwnd, msg, wp, lp); } return 0; } ``` ### 在 iOS (macOS) 上运行示例 要在 _iOS_ 设备上执行该示例,您需要设置签名信息。打开之前由 _CMake_ 生成的 _Xcode_ 项目。您可以在构建目标中找到 `example`。在 `Signing & Capabilities` 编辑器中(在 target 'example' 中)选择一个开发团队。 您现在可以点击 _Xcode_ 中的 `Run` 按钮,在设备或模拟器上启动示例应用程序。或者,您可以直接在终端窗口中构建示例应用程序: ``` # 为 iOS 构建 Chronos 示例 # (请确保此前已生成 iOS 的 Xcode 项目) cmake --build ./build-ios --target example --config Release # 为 macOS 构建 Chronos 示例 # (请确保此前已生成 macOS 的 Xcode 项目) cmake --build ./build-macos --target example --config Release ``` 要在 _iOS_ 设备上安装该应用程序,请打开 _Xcode_ 并使用 `Windows > Devices` 命令。从左侧的列表中选择您已连接的设备,然后点击 `Installed Apps` 下的 `Add`(加号)按钮,选择您构建的 `.ipa` 文件来添加该应用程序。 ### 在 Android 上运行示例 在 _Android Studio_ 中打开项目的根目录,您可以看到名为 `example` 的模块。您现在可以点击 `Run` 按钮,在设备或模拟器上启动示例应用程序。 或者,您可以在终端/控制台窗口中使用以下命令构建示例应用程序: ``` # 确保您当前的工作目录是项目根目录 cd chronos # 构建 Chronos 示例应用程序 ./gradlew :example:assembleRelease ``` 执行构建命令后,您可以在 `build-android/example/outputs/apk/release` 目录中找到 `.apk` 文件。 然后,您可以使用 _adb_ 将构建好的应用程序安装到设备或模拟器上。 ### 在 Web 上运行示例 要在 _Web_ 上运行该示例,您首先需要为 _Web_ 平台构建 _Chronos Engine_。为 _Web_ 构建 _Chronos Engine_ 之后,您可以使用以下命令通过本地服务器来提供示例文件: ``` # 确保您当前的工作目录是项目根目录 cd chronos # 为 Web 构建 Chronos Engine npm install # 使用本地服务器提供示例文件 npm run serve-example ``` 现在,您可以打开浏览器并导航到 `http://localhost:8080` 来查看示例。 ### 在 Windows 上运行示例 您可以使用 _Visual Studio_ 打开由 _CMake_ 生成的解决方案文件(`build-win32/chronos.sln`)。打开解决方案后,选择示例 target 来运行或调试示例程序。 或者,您可以使用以下命令直接从命令行构建示例程序: ``` # 为 Windows 构建 Chronos 示例 cmake --build ./build-win32 --target example --config Release ``` ## 高级功能 要探索 _Chronos Engine_ 的全部功能,请参阅位于以下目录中特定于平台的公共接口定义: - **iOS 和 macOS**:参见 `library/darwin/include/Chronos` 下的头文件 - **Android**:运行 `./gradlew :chronos:javadoc` 并打开生成在 `library/android/build/docs/javadoc/index.html` 的文档 - **Web**:参阅位于 `library/wasm/types.d.ts` 的 TypeScript 声明文件 - **Windows (Win32)**:参见 `library/windows/include/chronos` 下的头文件 以下部分概述了一些具有代表性的功能,并提供了在 _iOS_、_macOS_ 和 _Android_ 上的使用示例。请注意,某些功能可能在 _Web_ 平台上不可用。 ### 查看 Chronos Package 的元数据 您可以通过 _Chronos Engine_ 提供的 API 访问 _Chronos Package_ 的 `info.json` 中的元数据。这些元数据是作者在创建 _Chronos Package_ 时嵌入其中的。 `info.json` 通常包含以下四个字段:`name`、`author`、`version` 和 `description`,但 package 作者也可以包含额外的自定义字段。 这里有一些示例代码。对于 _iOS_ 或 _macOS_: ``` CRONPackage* cronPackage = [[CRONPackage alloc] initWithContentsOfFile:path]; NSDictionary* info = cronPackage.info; if (info) { NSLog(@"Package name: %@", info[@"name"]); NSLog(@"Package author: %@", info[@"author"]); NSLog(@"Package version: %@", info[@"version"]); NSLog(@"Package description: %@", info[@"description"]); } ``` 对于 _Android_: ``` ChronosPackage chronosPackage = ChronosPackage.createPackageFromFile(context, new File(path)); if (chronosPackage != null) { String info = chronosPackage.getInfo(); if (info != null) { JSONObject obj = new JSONObject(info); Log.i("chronos_demo", "Package name:" + obj.optString("name")); Log.i("chronos_demo", "Package author:" + obj.optString("author")); Log.i("chronos_demo", "Package version:" + obj.optString("version")); Log.i("chronos_demo", "Package description:" + obj.optString("description")); } } ``` ### 预加载 Chronos Package 当您创建一个 _Chronos Package_ 对象(对于 _iOS_ 或 _macOS_ 是 `CRONPackage`,对于 _Android_ 是 `ChronosPackage`)时,只有该 package 的元数据被加载到内存中。相关的脚本和资源文件在该 package 即将运行之前不会被解压。您可以手动触发此解压过程。 这里有一些示例代码。对于 _iOS_ 或 _macOS_: ``` CRONPackage* cronPackage = [[CRONPackage alloc] initWithContentsOfFile:path]; [cronPackage preloadWithCompletionHandler:^(bool success) { if (success) { [self.cronView runPackage:cronPackage completionHandler:nil]; } else { NSLog(@"Failed to load the Chronos Package!"); } }]; ``` 对于 _Android_: ``` ChronosPackage chronosPackage = ChronosPackage.createPackageFromFile(context, new File(path)); chronosPackage.preloadAsync(new ChronosPackage.LoadCompleteCallback() { @Override public void onComplete(boolean success) { if (success) { chronosView.runPackage(chronosPackage, null); } else { Log.e("chronos_demo", "Failed to load the Chronos Package!"); } } }); ``` ### 宿主应用与 Chronos Package 之间的通信 在某些情况下,宿主应用和 _Chronos Package_ 需要相互通信,特别是当 _Chronos Package_ 需要 _Chronos_ API 未提供的功能(例如与网络相关的功能)时。为了适应这些情况,_Chronos_ 提供了一种消息传递机制。 下面是一个示例,说明如何使用 _Chronos Engine_ 提供的通信 API 在宿主应用和 _Chronos Package_ 之间构建一个 RPC(远程过程调用)系统。 这里有一些示例代码。对于 _iOS_ 或 _macOS_: ``` #import "ViewController.h" #import @interface ViewController () @property(strong, nonatomic) CRONView* cronView; // Other properties ... @end @implementation ViewController typedef void (^RPCCallback)(id _Nullable response); - (void)handleRPCRequest:(nonnull NSString*)name arguments:(nonnull NSDictionary*)args completionHandler:(nonnull RPCCallback)completionHandler { // Handle the RPC request from the Chronos Package // Pass the return value back to the Chronos Package by calling the `completionHandler` // Omitting implementation ... } - (void)sendRPCRequest:(nonnull NSString*)name arguments:(nonnull NSDictionary*)args completionHandler:(nullable RPCCallback)completionHandler { // Serialize the procedure name and arguments to JSON data NSDictionary* requestObject = @{@"name": name, @"args": args}; NSData* requestData = [NSJSONSerialization dataWithJSONObject:requestObject options:0 error:NULL]; // Send the serialized JSON data to the Chronos Package [self.cronView sendMessageAsync:requestData completionHandler:^(NSData * _Nullable response) { // Received a response from the Chronos Package // Deserialize the JSON data to obtain the return value id responseObject = nil; if (response) { responseObject = [NSJSONSerialization JSONObjectWithData:response options:0 error:NULL]; } // Pass the deserialized return value if (completionHandler) { completionHandler(responseObject); } }]; } - (void)viewDidLoad { [super viewDidLoad]; // Use weak references to prevent circular references __weak ViewController* weakSelf = self; // Register a callback to handle raw messages from the Chronos Package self.cronView.messageHandler = ^(NSData* _Nonnull message, CRONMessageHandleCompletionHandler _Nonnull completionHandler) { // Received a message from the Chronos Package // Deserialize the JSON message to obtain the procedure name and arguments for the RPC call id requestJSON = [NSJSONSerialization JSONObjectWithData:message options:0 error:NULL]; NSAssert([requestJSON isKindOfClass:[NSDictionary class]], @"Invalid message format!"); NSAssert([requestJSON[@"name"] isKindOfClass:[NSString class]], @"Invalid procedure name!"); NSAssert([requestJSON[@"args"] isKindOfClass:[NSDictionary class]], @"Invalid arguments!"); NSString* name = requestJSON[@"name"]; NSDictionary* args = requestJSON[@"args"]; // Perform the actual procedure call based on the previously parsed name and arguments ViewController* strongSelf = weakSelf; [strongSelf handleRPCRequest:name arguments:args completionHandler:^(id _Nullable response) { // Serialize the return value to JSON data NSData* responseData = nil; if (response) { responseData = [NSJSONSerialization dataWithJSONObject:response options:0 error:NULL]; } // Pass the serialized return value back to the Chronos Package completionHandler(responseData); }]; }; // Run a Chronos Package ... // Send an RPC request to the Chronos Package [self sendRPCRequest:@"test" arguments:@{} completionHandler:^(id _Nullable response) { // Received the return value from the Chronos Package NSLog(@"result from the Chronos Package: %@", response); }]; } // Other methods ... @end ``` 对于 _Android_: ``` import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; import java.lang.ref.WeakReference; import java.nio.charset.StandardCharsets; import com.bilibili.cron.ChronosPackage; import com.bilibili.cron.ChronosPackageRunner; import com.bilibili.cron.ChronosView; class YourActivity extends Activity { ChronosView chronosView; private interface RPCCallback { void onComplete(@Nullable JSONObject result); } private void handleRPCRequest(@NonNull String name, @NonNull JSONObject args, @Nullable RPCCallback callback) { // Handle the RPC request from the Chronos Package // Pass the return value back to the Chronos Package by calling the `callback.onComplete` // Omitting implementation ... } private void sendRPCRequest(@NonNull String name, @NonNull JSONObject args, @Nullable RPCCallback callback) { // Serialize the procedure name and arguments to JSON JSONObject requestObject = new JSONObject(); try { requestObject.put("name", name); requestObject.put("args", args); } catch (JSONException e) { throw new RuntimeException(e); } byte[] requestData = requestObject.toString().getBytes(StandardCharsets.UTF_8); // Send the serialized JSON data to the Chronos Package chronosView.sendMessageAsync(requestData, (response) -> { // Received a response from the Chronos Package // Deserialize the JSON data to obtain the return value JSONObject responseObject = null; if (response != null) { try { responseObject = new JSONObject(new String(response, StandardCharsets.UTF_8)); } catch (JSONException e) { throw new RuntimeException(e); } } // Pass the deserialized return value back to the caller if (callback != null) { callback.onComplete(responseObject); } }); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); chronosView = findViewById(R.id.chronos); // Use weak references to prevent circular references WeakReference weakThis = new WeakReference<>(this); // Sets a message handler for ChronosView to handle message from the Chronos Package chronosView.setMessageHandler((message, callback) -> { // Received a message from the Chronos Package // Deserialize the JSON message to obtain the procedure name and arguments for the RPC call JSONObject requestObject; try { requestObject = new JSONObject(new String(message, StandardCharsets.UTF_8)); } catch (JSONException e) { throw new RuntimeException(e); } String name = requestObject.optString("name"); assert !name.isEmpty(); JSONObject args = requestObject.optJSONObject("args"); assert args != null; // Perform the actual procedure call based on the previously parsed name and arguments YourActivity strongThis = weakThis.get(); assert strongThis != null; strongThis.handleRPCRequest(name, args, response -> { // Serialize the return value to JSON data byte[] responseData = null; if (response != null) { responseData = response.toString().getBytes(StandardCharsets.UTF_8); } // Pass the serialized return value back to the Chronos Package callback.onComplete(responseData); }); }); // Run a Chronos Package ... // Send an RPC request to the Chronos Package sendRPCRequest("test", new JSONObject(), (result) -> { // Received the return value from the Chronos Package Log.i("chronos_demo", "result from the Chronos Package: " + result); }); } @Override protected void onDestroy() { // Release the ChronosView chronosView.release(); super.onDestroy(); } } ``` ### Chronos Package 文件系统 _Chronos Package_ 所需的大部分资源文件都是直接从 package 文件本身中提取的。不过,也有一些方法可以向 _Chronos Package_ 提供额外的资源。 例如,如果您有一些所有 _Chronos Package_ 都会使用的公共资源,并且希望将它们嵌入到您的应用中以减小 _Chronos Package_ 文件的大小,您可以为 _Chronos Engine_ 设置一个额外的资源搜索路径。这样 _Chronos Package_ 就可以从指定的路径访问资源文件了。 此外,_Chronos Package_ 的沙盒目录允许在宿主应用和 _Chronos Package_ 之间进行文件共享。宿主应用和 _Chronos Package_ 都可以读取和写入沙盒目录中的文件。这提供了另一种在宿主应用和 _Chronos Package_ 之间交换数据的方式。 这里有一些示例代码。对于 _iOS_ 或 _macOS_: ``` // Set external resource search paths self.cronView.searchPaths = @[ @"", @"" ]; CRONPackage* cronPackage = [[CRONPackage alloc] initWithContentsOfFile:@""]; // Get the sandbox directory of your Chronos Package NSLog(@"The sandbox directory of your package: %@", cronPackage.sandboxDirectory); [self.cronView runPackage:cronPackage completionHandler:nil]; ``` 对于 _Android_: ``` // Set external resource search paths chronosView.setResourceSearchPaths(new String[] { "", "" }); ChronosPackage chronosPackage = ChronosPackage.createPackageFromFile(context, new File("")); // Get the sandbox directory of your Chronos Package Log.i("chronos_demo", "The sandbox directory of your package: " + chronosPackage.getSandBoxDirectory()); chronosView.runPackage(chronosPackage, null); ``` ## 致谢 _Chronos_ 的架构和设计深受 [SpriteKit](https://developer.apple.com/spritekit/) 和 [Flutter](https://github.com/flutter/flutter) 的启发。我们特别感谢这两个项目所开创的想法和模式。 ## 许可证 _Chronos Engine_ 采用 [Apache License 2.0](LICENSE) 授权。与引擎一起打包的第三方组件及其各自的许可证列在 [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md) 中。
标签:AI工具, Bash脚本, CVE监控, 云安全, 后台面板检测, 数据可视化