chronos-kit/chronos
GitHub: chronos-kit/chronos
一个高性能的跨平台 2D/3D 内容渲染与动画框架,支持将复杂的视觉效果和互动游戏集成到移动端和 Web 应用中。
Stars: 37 | Forks: 3
案例展示 |
入门指南 |
高级功能 |
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监控, 云安全, 后台面板检测, 数据可视化