feat: 集成Rust WASM渲染引擎与TypeScript ECS框架 (#228)

* feat: 集成Rust WASM渲染引擎与TypeScript ECS框架

* feat: 增强编辑器UI功能与跨平台支持

* fix: 修复CI测试和类型检查问题

* fix: 修复CI问题并提高测试覆盖率

* fix: 修复CI问题并提高测试覆盖率
This commit is contained in:
YHH
2025-11-21 10:03:18 +08:00
committed by GitHub
parent 8b9616837d
commit a768b890fd
107 changed files with 10221 additions and 477 deletions

View File

@@ -0,0 +1,153 @@
//! WebGL context management.
//! WebGL上下文管理。
use web_sys::{HtmlCanvasElement, WebGl2RenderingContext};
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use super::error::{EngineError, Result};
/// WebGL2 rendering context wrapper.
/// WebGL2渲染上下文包装器。
///
/// Manages the WebGL2 context and provides helper methods for common operations.
/// 管理WebGL2上下文并提供常用操作的辅助方法。
pub struct WebGLContext {
/// The WebGL2 rendering context.
/// WebGL2渲染上下文。
gl: WebGl2RenderingContext,
/// The canvas element.
/// Canvas元素。
canvas: HtmlCanvasElement,
}
impl WebGLContext {
/// Create a new WebGL context from a canvas ID.
/// 从canvas ID创建新的WebGL上下文。
///
/// # Arguments | 参数
/// * `canvas_id` - The ID of the canvas element | canvas元素的ID
///
/// # Returns | 返回
/// A new WebGLContext or an error | 新的WebGLContext或错误
pub fn new(canvas_id: &str) -> Result<Self> {
// Get document and canvas | 获取document和canvas
let window = web_sys::window().expect("No window found | 未找到window");
let document = window.document().expect("No document found | 未找到document");
let canvas = document
.get_element_by_id(canvas_id)
.ok_or_else(|| EngineError::CanvasNotFound(canvas_id.to_string()))?
.dyn_into::<HtmlCanvasElement>()
.map_err(|_| EngineError::CanvasNotFound(canvas_id.to_string()))?;
// Create WebGL2 context | 创建WebGL2上下文
let gl = canvas
.get_context("webgl2")
.map_err(|_| EngineError::ContextCreationFailed)?
.ok_or(EngineError::ContextCreationFailed)?
.dyn_into::<WebGl2RenderingContext>()
.map_err(|_| EngineError::ContextCreationFailed)?;
log::info!(
"WebGL2 context created | WebGL2上下文已创建: {}x{}",
canvas.width(),
canvas.height()
);
Ok(Self { gl, canvas })
}
/// Create a new WebGL context from external JavaScript objects.
/// 从外部 JavaScript 对象创建 WebGL 上下文。
///
/// This method is designed for environments like WeChat MiniGame
/// where the canvas is not a standard HTML element.
/// 此方法适用于微信小游戏等环境,其中 canvas 不是标准 HTML 元素。
pub fn from_external(
gl_context: JsValue,
canvas_width: u32,
canvas_height: u32,
) -> Result<Self> {
// Convert JsValue to WebGl2RenderingContext
let gl = gl_context
.dyn_into::<WebGl2RenderingContext>()
.map_err(|_| EngineError::ContextCreationFailed)?;
// Create a dummy canvas for compatibility
// In MiniGame environment, we don't have HtmlCanvasElement
let window = web_sys::window().ok_or(EngineError::ContextCreationFailed)?;
let document = window.document().ok_or(EngineError::ContextCreationFailed)?;
let canvas = document
.create_element("canvas")
.map_err(|_| EngineError::ContextCreationFailed)?
.dyn_into::<HtmlCanvasElement>()
.map_err(|_| EngineError::ContextCreationFailed)?;
canvas.set_width(canvas_width);
canvas.set_height(canvas_height);
log::info!(
"WebGL2 context created from external | 从外部创建WebGL2上下文: {}x{}",
canvas_width,
canvas_height
);
Ok(Self { gl, canvas })
}
/// Get a reference to the WebGL2 context.
/// 获取WebGL2上下文的引用。
#[inline]
pub fn gl(&self) -> &WebGl2RenderingContext {
&self.gl
}
/// Get a reference to the canvas element.
/// 获取canvas元素的引用。
#[inline]
pub fn canvas(&self) -> &HtmlCanvasElement {
&self.canvas
}
/// Get canvas width.
/// 获取canvas宽度。
#[inline]
pub fn width(&self) -> u32 {
self.canvas.width()
}
/// Get canvas height.
/// 获取canvas高度。
#[inline]
pub fn height(&self) -> u32 {
self.canvas.height()
}
/// Clear the canvas with specified color.
/// 使用指定颜色清除canvas。
pub fn clear(&self, r: f32, g: f32, b: f32, a: f32) {
self.gl.clear_color(r, g, b, a);
self.gl.clear(
WebGl2RenderingContext::COLOR_BUFFER_BIT | WebGl2RenderingContext::DEPTH_BUFFER_BIT,
);
}
/// Set the viewport to match canvas size.
/// 设置视口以匹配canvas大小。
pub fn set_viewport(&self) {
self.gl
.viewport(0, 0, self.width() as i32, self.height() as i32);
}
/// Enable alpha blending for transparency.
/// 启用透明度的alpha混合。
pub fn enable_blend(&self) {
self.gl.enable(WebGl2RenderingContext::BLEND);
self.gl.blend_func(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
);
}
}

View File

@@ -0,0 +1,187 @@
//! Main engine implementation.
//! 主引擎实现。
use wasm_bindgen::prelude::*;
use super::context::WebGLContext;
use super::error::Result;
use crate::input::InputManager;
use crate::renderer::Renderer2D;
use crate::resource::TextureManager;
/// Engine configuration options.
/// 引擎配置选项。
#[derive(Debug, Clone)]
pub struct EngineConfig {
/// Maximum sprites per batch.
/// 每批次最大精灵数。
pub max_sprites: usize,
/// Enable debug mode.
/// 启用调试模式。
pub debug: bool,
}
impl Default for EngineConfig {
fn default() -> Self {
Self {
max_sprites: 10000,
debug: false,
}
}
}
/// Main game engine.
/// 主游戏引擎。
///
/// Coordinates all engine subsystems including rendering, input, and resources.
/// 协调所有引擎子系统,包括渲染、输入和资源。
pub struct Engine {
/// WebGL context.
/// WebGL上下文。
context: WebGLContext,
/// 2D renderer.
/// 2D渲染器。
renderer: Renderer2D,
/// Texture manager.
/// 纹理管理器。
texture_manager: TextureManager,
/// Input manager.
/// 输入管理器。
input_manager: InputManager,
/// Engine configuration.
/// 引擎配置。
#[allow(dead_code)]
config: EngineConfig,
}
impl Engine {
/// Create a new engine instance.
/// 创建新的引擎实例。
///
/// # Arguments | 参数
/// * `canvas_id` - The HTML canvas element ID | HTML canvas元素ID
/// * `config` - Engine configuration | 引擎配置
///
/// # Returns | 返回
/// A new Engine instance or an error | 新的Engine实例或错误
pub fn new(canvas_id: &str, config: EngineConfig) -> Result<Self> {
let context = WebGLContext::new(canvas_id)?;
// Initialize WebGL state | 初始化WebGL状态
context.set_viewport();
context.enable_blend();
// Create subsystems | 创建子系统
let renderer = Renderer2D::new(context.gl(), config.max_sprites)?;
let texture_manager = TextureManager::new(context.gl().clone());
let input_manager = InputManager::new();
log::info!("Engine created successfully | 引擎创建成功");
Ok(Self {
context,
renderer,
texture_manager,
input_manager,
config,
})
}
/// Create a new engine instance from external WebGL context.
/// 从外部 WebGL 上下文创建引擎实例。
///
/// This is designed for environments like WeChat MiniGame.
/// 适用于微信小游戏等环境。
pub fn from_external(
gl_context: JsValue,
width: u32,
height: u32,
config: EngineConfig,
) -> Result<Self> {
let context = WebGLContext::from_external(gl_context, width, height)?;
context.set_viewport();
context.enable_blend();
let renderer = Renderer2D::new(context.gl(), config.max_sprites)?;
let texture_manager = TextureManager::new(context.gl().clone());
let input_manager = InputManager::new();
log::info!("Engine created from external context | 从外部上下文创建引擎");
Ok(Self {
context,
renderer,
texture_manager,
input_manager,
config,
})
}
/// Clear the screen with specified color.
/// 使用指定颜色清除屏幕。
pub fn clear(&self, r: f32, g: f32, b: f32, a: f32) {
self.context.clear(r, g, b, a);
}
/// Get canvas width.
/// 获取画布宽度。
#[inline]
pub fn width(&self) -> u32 {
self.context.width()
}
/// Get canvas height.
/// 获取画布高度。
#[inline]
pub fn height(&self) -> u32 {
self.context.height()
}
/// Submit sprite batch data for rendering.
/// 提交精灵批次数据进行渲染。
pub fn submit_sprite_batch(
&mut self,
transforms: &[f32],
texture_ids: &[u32],
uvs: &[f32],
colors: &[u32],
) -> Result<()> {
self.renderer.submit_batch(
transforms,
texture_ids,
uvs,
colors,
&self.texture_manager,
)
}
/// Render the current frame.
/// 渲染当前帧。
pub fn render(&mut self) -> Result<()> {
self.renderer.render(self.context.gl())
}
/// Load a texture from URL.
/// 从URL加载纹理。
pub fn load_texture(&mut self, id: u32, url: &str) -> Result<()> {
self.texture_manager.load_texture(id, url)
}
/// Check if a key is currently pressed.
/// 检查某个键是否当前被按下。
pub fn is_key_down(&self, key_code: &str) -> bool {
self.input_manager.is_key_down(key_code)
}
/// Update input state.
/// 更新输入状态。
pub fn update_input(&mut self) {
self.input_manager.update();
}
}

View File

@@ -0,0 +1,58 @@
//! Error types for the engine.
//! 引擎的错误类型定义。
use thiserror::Error;
/// Engine error types.
/// 引擎错误类型。
#[derive(Error, Debug)]
pub enum EngineError {
/// Canvas element not found.
/// 未找到Canvas元素。
#[error("Canvas element not found: {0} | 未找到Canvas元素: {0}")]
CanvasNotFound(String),
/// WebGL context creation failed.
/// WebGL上下文创建失败。
#[error("WebGL2 context creation failed | WebGL2上下文创建失败")]
ContextCreationFailed,
/// Shader compilation failed.
/// Shader编译失败。
#[error("Shader compilation failed: {0} | Shader编译失败: {0}")]
ShaderCompileFailed(String),
/// Shader program linking failed.
/// Shader程序链接失败。
#[error("Shader program linking failed: {0} | Shader程序链接失败: {0}")]
ProgramLinkFailed(String),
/// Texture loading failed.
/// 纹理加载失败。
#[error("Texture loading failed: {0} | 纹理加载失败: {0}")]
TextureLoadFailed(String),
/// Texture not found.
/// 未找到纹理。
#[error("Texture not found: {0} | 未找到纹理: {0}")]
TextureNotFound(u32),
/// Invalid batch data.
/// 无效的批处理数据。
#[error("Invalid batch data: {0} | 无效的批处理数据: {0}")]
InvalidBatchData(String),
/// Buffer creation failed.
/// 缓冲区创建失败。
#[error("Buffer creation failed | 缓冲区创建失败")]
BufferCreationFailed,
/// WebGL operation failed.
/// WebGL操作失败。
#[error("WebGL operation failed: {0} | WebGL操作失败: {0}")]
WebGLError(String),
}
/// Result type alias for engine operations.
/// 引擎操作的Result类型别名。
pub type Result<T> = std::result::Result<T, EngineError>;

View File

@@ -0,0 +1,10 @@
//! Core engine module containing lifecycle management and context.
//! 核心引擎模块,包含生命周期管理和上下文。
pub mod error;
pub mod context;
mod engine;
pub use engine::{Engine, EngineConfig};
pub use context::WebGLContext;
pub use error::{EngineError, Result};