optimization of wasm and component v4

This commit is contained in:
2026-04-24 05:40:56 +07:00
parent 59141fe227
commit 487e75a405
3 changed files with 277 additions and 22 deletions
+96 -20
View File
@@ -23,6 +23,7 @@ impl GlitchBlock {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let w = rng.gen_range(20.0..250.0); let w = rng.gen_range(20.0..250.0);
let h = rng.gen_range(5.0..50.0); let h = rng.gen_range(5.0..50.0);
let choice = rng.gen_range(0..100); let choice = rng.gen_range(0..100);
let color = match choice { let color = match choice {
0..=15 => bg_color.to_string(), 0..=15 => bg_color.to_string(),
@@ -31,6 +32,7 @@ impl GlitchBlock {
76..=95 => "rgba(255, 255, 255, 0.95)".to_string(), 76..=95 => "rgba(255, 255, 255, 0.95)".to_string(),
_ => "rgba(255, 20, 80, 0.95)".to_string(), _ => "rgba(255, 20, 80, 0.95)".to_string(),
}; };
Self { Self {
x: x - w / 2.0 + rng.gen_range(-120.0..120.0), x: x - w / 2.0 + rng.gen_range(-120.0..120.0),
y: y - h / 2.0 + rng.gen_range(-120.0..120.0), y: y - h / 2.0 + rng.gen_range(-120.0..120.0),
@@ -61,6 +63,7 @@ impl GlassSymbol {
(true, false) => "rgb(90, 30, 200)", (true, false) => "rgb(90, 30, 200)",
(false, false) => "rgb(0, 140, 160)", (false, false) => "rgb(0, 140, 160)",
}; };
Self { Self {
x: x + rng.gen_range(-15.0..15.0), x: x + rng.gen_range(-15.0..15.0),
y: y + rng.gen_range(-15.0..15.0), y: y + rng.gen_range(-15.0..15.0),
@@ -83,6 +86,10 @@ pub struct MatrixEngine {
glitch_blocks: Vec<GlitchBlock>, glitch_blocks: Vec<GlitchBlock>,
charset: Vec<char>, charset: Vec<char>,
ctx: CanvasRenderingContext2d, ctx: CanvasRenderingContext2d,
// Тот самый секретный холст для спрайтов
atlas_canvas: HtmlCanvasElement,
mouse_x: f32, mouse_x: f32,
mouse_y: f32, mouse_y: f32,
is_drawing: bool, is_drawing: bool,
@@ -106,11 +113,23 @@ impl MatrixEngine {
.dyn_into::<CanvasRenderingContext2d>()?; .dyn_into::<CanvasRenderingContext2d>()?;
let width = canvas.width(); let width = canvas.width();
let height = canvas.height(); let height = canvas.height();
let chars_str = "0101アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホ마미무메모야유요라릴레로와원>_±÷×=≠≈≡≤≥"; let chars_str = "0101アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホ마미무메모야유요라릴레로와원>_±÷×=≠≈≡≤≥";
let charset: Vec<char> = chars_str.chars().collect(); let charset: Vec<char> = chars_str.chars().collect();
let columns = width / font_size; let columns = width / font_size;
// --- СОЗДАЕМ ТЕКСТУРНЫЙ АТЛАС В ПАМЯТИ ---
let document = web_sys::window().unwrap().document().unwrap();
let atlas_canvas = document
.create_element("canvas")?
.dyn_into::<HtmlCanvasElement>()?;
// Ширина = количество символов * размер. Высота = 2 ряда (цветной и белый)
atlas_canvas.set_width((charset.len() as u32) * font_size);
atlas_canvas.set_height(font_size * 2);
let mut drops = Vec::new(); let mut drops = Vec::new();
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
for _ in 0..columns { for _ in 0..columns {
drops.push(Drop { drops.push(Drop {
y: rng.gen_range(-20.0..0.0), y: rng.gen_range(-20.0..0.0),
@@ -118,7 +137,8 @@ impl MatrixEngine {
glitch_timer: 0.0, glitch_timer: 0.0,
}); });
} }
Ok(MatrixEngine {
let mut engine = MatrixEngine {
width, width,
height, height,
font_size, font_size,
@@ -127,6 +147,7 @@ impl MatrixEngine {
glitch_blocks: Vec::new(), glitch_blocks: Vec::new(),
charset, charset,
ctx, ctx,
atlas_canvas,
mouse_x: -1000.0, mouse_x: -1000.0,
mouse_y: -1000.0, mouse_y: -1000.0,
is_drawing: false, is_drawing: false,
@@ -138,7 +159,51 @@ impl MatrixEngine {
emp_y: 0.0, emp_y: 0.0,
tear_y: 0.0, tear_y: 0.0,
tear_timer: 0.0, tear_timer: 0.0,
}) };
// Генерируем спрайты первый раз
engine.build_atlas();
Ok(engine)
}
// МЕТОД ГЕНЕРАЦИИ СПРАЙТОВ (Вызывается 1 раз при загрузке или смене темы)
fn build_atlas(&self) {
let atlas_ctx = self
.atlas_canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<CanvasRenderingContext2d>()
.unwrap();
let (r, g, b) = self.get_theme_colors();
atlas_ctx.clear_rect(
0.0,
0.0,
self.atlas_canvas.width() as f64,
self.atlas_canvas.height() as f64,
);
atlas_ctx.set_font(&format!("bold {}px monospace", self.font_size));
atlas_ctx.set_text_baseline("top");
atlas_ctx.set_text_align("center"); // Рисуем строго по центру клетки
let half_fs = self.font_size as f64 / 2.0;
// 1 РЯД: Базовый цвет (тема)
atlas_ctx.set_fill_style_str(&format!("rgb({}, {}, {})", r, g, b));
for (i, c) in self.charset.iter().enumerate() {
let x = (i as u32 * self.font_size) as f64 + half_fs;
atlas_ctx.fill_text(&c.to_string(), x, 0.0).unwrap_or(());
}
// 2 РЯД: Белый цвет (головки капель и EMP вспышки)
atlas_ctx.set_fill_style_str("rgb(255, 255, 255)");
for (i, c) in self.charset.iter().enumerate() {
let x = (i as u32 * self.font_size) as f64 + half_fs;
let y = self.font_size as f64;
atlas_ctx.fill_text(&c.to_string(), x, y).unwrap_or(());
}
} }
pub fn resize(&mut self, width: u32, height: u32) { pub fn resize(&mut self, width: u32, height: u32) {
@@ -163,9 +228,13 @@ impl MatrixEngine {
pub fn set_drawing(&mut self, is_drawing: bool) { pub fn set_drawing(&mut self, is_drawing: bool) {
self.is_drawing = is_drawing; self.is_drawing = is_drawing;
} }
pub fn set_theme(&mut self, is_vpn_on: bool, is_dark_mode: bool) { pub fn set_theme(&mut self, is_vpn_on: bool, is_dark_mode: bool) {
if self.is_vpn_on != is_vpn_on || self.is_dark_mode != is_dark_mode {
self.is_vpn_on = is_vpn_on; self.is_vpn_on = is_vpn_on;
self.is_dark_mode = is_dark_mode; self.is_dark_mode = is_dark_mode;
self.build_atlas(); // Перерисовываем спрайты под новые цвета!
}
} }
pub fn trigger_glitch(&mut self, x: f32, y: f32) { pub fn trigger_glitch(&mut self, x: f32, y: f32) {
@@ -210,13 +279,9 @@ impl MatrixEngine {
self.ctx.set_fill_style_str(bg_fill); self.ctx.set_fill_style_str(bg_fill);
self.ctx self.ctx
.fill_rect(0.0, 0.0, self.width as f64, self.height as f64); .fill_rect(0.0, 0.0, self.width as f64, self.height as f64);
self.ctx
.set_font(&format!("bold {}px monospace", self.font_size));
let (r, g, b) = self.get_theme_colors(); let (r, g, b) = self.get_theme_colors();
let bg_solid = self.get_bg_color_rgba(); let bg_solid = self.get_bg_color_rgba();
let color_base = format!("rgb({}, {}, {})", r, g, b);
let color_head = "rgb(255, 255, 255)";
if rng.gen_bool(0.02) { if rng.gen_bool(0.02) {
let rx = rng.gen_range(0.0..self.width as f32); let rx = rng.gen_range(0.0..self.width as f32);
@@ -238,8 +303,8 @@ impl MatrixEngine {
} }
for i in 0..self.drops.len() { for i in 0..self.drops.len() {
let mut x = (i as f32) * (self.font_size as f32);
let drop = &mut self.drops[i]; let drop = &mut self.drops[i];
let mut x = (i as f32) * (self.font_size as f32);
let cell_y = drop.y.floor(); let cell_y = drop.y.floor();
let y = cell_y * (self.font_size as f32); let y = cell_y * (self.font_size as f32);
@@ -285,34 +350,42 @@ impl MatrixEngine {
.wrapping_add(time_shift); .wrapping_add(time_shift);
let mut char_idx = (hash as usize) % self.charset.len(); let mut char_idx = (hash as usize) % self.charset.len();
let is_head = rng.gen_bool(0.15); let mut is_head = rng.gen_bool(0.15);
let mut target_alpha = if is_head { 1.0 } else { 0.6 }; let mut target_alpha = if is_head { 1.0 } else { 0.6 };
let mut target_color = if is_head {
color_head
} else {
color_base.as_str()
};
if is_emp_ring { if is_emp_ring {
target_color = color_head; is_head = true;
target_alpha = 1.0; target_alpha = 1.0;
char_idx = rng.gen_range(0..self.charset.len()); char_idx = rng.gen_range(0..self.charset.len());
} else if is_scrambled || drop.glitch_timer > 0.0 { } else if is_scrambled || drop.glitch_timer > 0.0 {
char_idx = rng.gen_range(0..self.charset.len()); char_idx = rng.gen_range(0..self.charset.len());
target_alpha = rng.gen_range(0.6..1.0); target_alpha = rng.gen_range(0.6..1.0);
if rng.gen_bool(0.2) { is_head = rng.gen_bool(0.2);
target_color = color_head;
}
if drop.glitch_timer > 0.0 { if drop.glitch_timer > 0.0 {
drop.glitch_timer -= 1.0; drop.glitch_timer -= 1.0;
} }
} }
// --- БЫСТРЕЙШАЯ В МИРЕ ОТРИСОВКА (Копирование пикселей) ---
self.ctx.set_global_alpha(target_alpha as f64); self.ctx.set_global_alpha(target_alpha as f64);
self.ctx.set_fill_style_str(target_color);
let char_str = self.charset[char_idx].to_string(); // Вычисляем координаты куска спрайта из атласа
let src_x = (char_idx as u32 * self.font_size) as f64;
let src_y = if is_head { self.font_size as f64 } else { 0.0 }; // 0-й ряд цветной, 1-й ряд белый
let f = self.font_size as f64;
self.ctx self.ctx
.fill_text(&char_str, x as f64, y as f64) .draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
&self.atlas_canvas,
src_x,
src_y,
f,
f, // Откуда берем (X, Y, Ширина, Высота)
x as f64,
y as f64,
f,
f, // Куда кладем
)
.unwrap_or(()); .unwrap_or(());
drop.y += drop.speed; drop.y += drop.speed;
@@ -335,6 +408,7 @@ impl MatrixEngine {
} }
self.glitch_blocks.retain(|b| b.life > 0.0); self.glitch_blocks.retain(|b| b.life > 0.0);
// Интерактивные символы от мышки рисуем по старинке, их мало, они не тормозят
if self.is_drawing && rng.gen_bool(0.3) { if self.is_drawing && rng.gen_bool(0.3) {
self.glass_symbols.push(GlassSymbol::new( self.glass_symbols.push(GlassSymbol::new(
self.mouse_x, self.mouse_x,
@@ -345,6 +419,8 @@ impl MatrixEngine {
)); ));
} }
self.ctx
.set_font(&format!("bold {}px monospace", self.font_size));
for sym in self.glass_symbols.iter_mut() { for sym in self.glass_symbols.iter_mut() {
sym.y += sym.velocity_y; sym.y += sym.velocity_y;
sym.life -= 1.0; sym.life -= 1.0;
+179
View File
@@ -66,6 +66,13 @@ if (Symbol.dispose) MatrixEngine.prototype[Symbol.dispose] = MatrixEngine.protot
function __wbg_get_imports() { function __wbg_get_imports() {
const import0 = { const import0 = {
__proto__: null, __proto__: null,
__wbg___wbindgen_debug_string_ab4b34d23d6778bd: function(arg0, arg1) {
const ret = debugString(arg1);
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
},
__wbg___wbindgen_is_function_3baa9db1a987f47d: function(arg0) { __wbg___wbindgen_is_function_3baa9db1a987f47d: function(arg0) {
const ret = typeof(arg0) === 'function'; const ret = typeof(arg0) === 'function';
return ret; return ret;
@@ -90,10 +97,24 @@ function __wbg_get_imports() {
const ret = arg0.call(arg1, arg2); const ret = arg0.call(arg1, arg2);
return ret; return ret;
}, arguments); }, }, arguments); },
__wbg_clearRect_5fb1d6b44e6b6738: function(arg0, arg1, arg2, arg3, arg4) {
arg0.clearRect(arg1, arg2, arg3, arg4);
},
__wbg_createElement_bbd4c90086fe6f7b: function() { return handleError(function (arg0, arg1, arg2) {
const ret = arg0.createElement(getStringFromWasm0(arg1, arg2));
return ret;
}, arguments); },
__wbg_crypto_38df2bab126b63dc: function(arg0) { __wbg_crypto_38df2bab126b63dc: function(arg0) {
const ret = arg0.crypto; const ret = arg0.crypto;
return ret; return ret;
}, },
__wbg_document_7a41071f2f439323: function(arg0) {
const ret = arg0.document;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
},
__wbg_drawImage_2b01d8026be6b7ab: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
arg0.drawImage(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
}, arguments); },
__wbg_fillRect_992c5a4646ea7a7f: function(arg0, arg1, arg2, arg3, arg4) { __wbg_fillRect_992c5a4646ea7a7f: function(arg0, arg1, arg2, arg3, arg4) {
arg0.fillRect(arg1, arg2, arg3, arg4); arg0.fillRect(arg1, arg2, arg3, arg4);
}, },
@@ -121,6 +142,26 @@ function __wbg_get_imports() {
const ret = result; const ret = result;
return ret; return ret;
}, },
__wbg_instanceof_HtmlCanvasElement_ea4dfc3bb77c734b: function(arg0) {
let result;
try {
result = arg0 instanceof HTMLCanvasElement;
} catch (_) {
result = false;
}
const ret = result;
return ret;
},
__wbg_instanceof_Window_cc64c86c8ef9e02b: function(arg0) {
let result;
try {
result = arg0 instanceof Window;
} catch (_) {
result = false;
}
const ret = result;
return ret;
},
__wbg_length_9f1775224cf1d815: function(arg0) { __wbg_length_9f1775224cf1d815: function(arg0) {
const ret = arg0.length; const ret = arg0.length;
return ret; return ret;
@@ -160,6 +201,18 @@ function __wbg_get_imports() {
__wbg_set_globalAlpha_1660b0603161d11b: function(arg0, arg1) { __wbg_set_globalAlpha_1660b0603161d11b: function(arg0, arg1) {
arg0.globalAlpha = arg1; arg0.globalAlpha = arg1;
}, },
__wbg_set_height_be9b2b920bd68401: function(arg0, arg1) {
arg0.height = arg1 >>> 0;
},
__wbg_set_textAlign_e1a83482b00339b8: function(arg0, arg1, arg2) {
arg0.textAlign = getStringFromWasm0(arg1, arg2);
},
__wbg_set_textBaseline_8662e97190d0164a: function(arg0, arg1, arg2) {
arg0.textBaseline = getStringFromWasm0(arg1, arg2);
},
__wbg_set_width_5cda41d4d06a14dd: function(arg0, arg1) {
arg0.width = arg1 >>> 0;
},
__wbg_static_accessor_GLOBAL_8cfadc87a297ca02: function() { __wbg_static_accessor_GLOBAL_8cfadc87a297ca02: function() {
const ret = typeof global === 'undefined' ? null : global; const ret = typeof global === 'undefined' ? null : global;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
@@ -224,11 +277,84 @@ function addToExternrefTable0(obj) {
return idx; return idx;
} }
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches && builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
function getArrayU8FromWasm0(ptr, len) { function getArrayU8FromWasm0(ptr, len) {
ptr = ptr >>> 0; ptr = ptr >>> 0;
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
} }
let cachedDataViewMemory0 = null;
function getDataViewMemory0() {
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
}
return cachedDataViewMemory0;
}
function getStringFromWasm0(ptr, len) { function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0; ptr = ptr >>> 0;
return decodeText(ptr, len); return decodeText(ptr, len);
@@ -255,6 +381,43 @@ function isLikeNone(x) {
return x === undefined || x === null; return x === undefined || x === null;
} }
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8ArrayMemory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = cachedTextEncoder.encodeInto(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function takeFromExternrefTable0(idx) { function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_externrefs.get(idx); const value = wasm.__wbindgen_externrefs.get(idx);
wasm.__externref_table_dealloc(idx); wasm.__externref_table_dealloc(idx);
@@ -275,10 +438,26 @@ function decodeText(ptr, len) {
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
} }
const cachedTextEncoder = new TextEncoder();
if (!('encodeInto' in cachedTextEncoder)) {
cachedTextEncoder.encodeInto = function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
};
}
let WASM_VECTOR_LEN = 0;
let wasmModule, wasm; let wasmModule, wasm;
function __wbg_finalize_init(instance, module) { function __wbg_finalize_init(instance, module) {
wasm = instance.exports; wasm = instance.exports;
wasmModule = module; wasmModule = module;
cachedDataViewMemory0 = null;
cachedUint8ArrayMemory0 = null; cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start(); wasm.__wbindgen_start();
return wasm; return wasm;
Binary file not shown.