1use cvkg_core::{FrameRenderer, Renderer};
34#[cfg(target_os = "linux")]
36use std::sync::Arc;
37use winit::{
38 application::ApplicationHandler,
39 event::{DeviceId, DeviceEvent, WindowEvent},
40 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
41 window::{Window, WindowId},
42};
43
44
45pub struct NativeRenderer {
48 gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>,
49 delta_time: f32,
50 elapsed_time: f32,
51 berserker_mode: cvkg_core::BerserkerMode,
52 rage: f32,
53 window: Arc<Window>,
54}
55
56#[derive(Debug)]
58enum AppEvent {
59 AccessibilityAction(accesskit::ActionRequest),
60}
61
62impl NativeRenderer {
63 fn new(window: Arc<Window>, gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>, delta_time: f32, elapsed_time: f32, berserker_mode: cvkg_core::BerserkerMode, rage: f32) -> Self {
65 Self { gpu, delta_time, elapsed_time, berserker_mode, rage, window }
66 }
67
68 pub fn run<V: cvkg_core::View + 'static>(view: V) {
71 let event_loop = EventLoop::<AppEvent>::with_user_event()
72 .build()
73 .expect("Failed to create event loop");
74 event_loop.set_control_flow(ControlFlow::Wait);
75
76 let mut app = App {
77 view,
78 windows: std::collections::HashMap::new(),
79 gpu: None,
80 asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
81 proxy: event_loop.create_proxy(),
82 start_time: std::time::Instant::now(),
83 last_frame_time: std::time::Instant::now(),
84 berserker_mode: cvkg_core::BerserkerMode::Normal,
85 rage: 0.0,
86 };
87
88 event_loop.run_app(&mut app).expect("Event loop error");
89 }
90}
91
92struct WindowState {
93 window: Arc<Window>,
94 accesskit_adapter: Option<accesskit_winit::Adapter>,
95 vdom: Option<cvkg_vdom::VDom>,
96 cursor_pos: [f32; 2],
97 last_redraw_start: std::time::Instant,
99 frame_history: std::collections::VecDeque<f32>,
101 frame_count: u64,
103 last_pos: Option<[i32; 2]>,
105}
106
107struct App<V: cvkg_core::View> {
108 view: V,
109 windows: std::collections::HashMap<WindowId, WindowState>,
110 gpu: Option<Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>>,
111 #[allow(dead_code)]
112 asset_manager: std::sync::Arc<NativeAssetManager>,
113 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
114 start_time: std::time::Instant,
115 last_frame_time: std::time::Instant,
116 berserker_mode: cvkg_core::BerserkerMode,
117 rage: f32,
118}
119
120impl<V: cvkg_core::View + 'static> ApplicationHandler<AppEvent> for App<V> {
121 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
122 if self.gpu.is_none() {
123 log::info!("[Native] App instance (resumed): {:p}", self);
124
125 let window_attrs = Window::default_attributes()
126 .with_title("CVKG Berserker")
127 .with_visible(true)
128 .with_transparent(false)
129 .with_decorations(true)
130 .with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0));
131
132 let window = Arc::new(
133 event_loop
134 .create_window(window_attrs)
135 .expect("Failed to create window"),
136 );
137
138 let window_id = window.id();
139 let vdom = cvkg_vdom::VDom::build(&self.view, cvkg_core::Rect::new(0.0, 0.0, 1280.0, 720.0));
140
141 log::info!("[Native] INSERTING window ID: {:?}", window_id);
142
143 self.windows.insert(window_id, WindowState {
144 window: window.clone(),
145 accesskit_adapter: None,
146 vdom: Some(vdom),
147 cursor_pos: [0.0, 0.0],
148 last_redraw_start: std::time::Instant::now(),
149 frame_history: std::collections::VecDeque::with_capacity(60),
150 frame_count: 0,
151 last_pos: None,
152 });
153
154 let gpu = pollster::block_on(cvkg_render_gpu::SurtrRenderer::forge(window.clone()));
156 self.gpu = Some(Arc::new(std::sync::Mutex::new(gpu)));
157
158 log::info!("[Native] Initialization complete.");
159 window.request_redraw();
160 }
161 }
162
163 fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
164 if matches!(cause, winit::event::StartCause::Poll) {
165 } else {
167 log::debug!("[Native] Event Loop Wake: {:?}", cause);
168 }
169 }
170
171 fn device_event(&mut self, _event_loop: &ActiveEventLoop, _device_id: winit::event::DeviceId, event: winit::event::DeviceEvent) {
172 if matches!(event, winit::event::DeviceEvent::MouseMotion { .. }) {
173 } else {
175 log::info!("[Native] DEVICE EVENT: {:?}", event);
176 }
177 }
178
179 fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
180 if !matches!(event, WindowEvent::RedrawRequested) && !matches!(event, WindowEvent::CursorMoved { .. }) {
181 log::info!("[Native] App instance: {:p} | WINDOW EVENT: {:?}", self, event);
182 }
183
184 let gpu_arc = if let Some(g) = &self.gpu {
185 g.clone()
186 } else {
187 log::warn!("[Native] DROPPING EVENT: GPU not initialized yet");
188 return;
189 };
190
191 let state = if let Some(s) = self.windows.get_mut(&id) {
192 s
193 } else {
194 return;
195 };
196
197 match event {
198 WindowEvent::Moved(pos) => {
199 let dx = state.last_pos.map_or(0, |last| pos.x - last[0]);
200 let dy = state.last_pos.map_or(0, |last| pos.y - last[1]);
201 let speed = ((dx.pow(2) + dy.pow(2)) as f32).sqrt();
202
203 if speed > 0.1 {
204 self.rage = (self.rage + 0.2).min(1.0);
206 log::info!("[Native] Kinetic Injection! Rage: {}", self.rage);
207 }
208
209 state.last_pos = Some([pos.x, pos.y]);
210 state.window.request_redraw();
211 }
212 WindowEvent::CloseRequested => {
213 self.windows.remove(&id);
214 if self.windows.is_empty() {
215 event_loop.exit();
216 }
217 }
218 WindowEvent::Resized(physical_size) => {
219 gpu_arc.lock().expect("GPU mutex poisoned during resize").resize(
224 id,
225 physical_size.width,
226 physical_size.height,
227 state.window.scale_factor() as f32,
228 );
229 state.window.request_redraw();
230 }
231 WindowEvent::Focused(focused) => {
232 log::info!("[Native] Window focus changed: {}", focused);
233 }
234 WindowEvent::RedrawRequested => {
235 if state.frame_count % 60 == 0 {
236 log::info!("[Native] RedrawRequested (frame {})", state.frame_count);
237 }
238 let size = state.window.inner_size();
239 let scale = state.window.scale_factor();
240 let logical_size = size.to_logical::<f32>(scale);
241
242 let rect = cvkg_core::Rect {
243 x: 0.0,
244 y: 0.0,
245 width: logical_size.width,
246 height: logical_size.height,
247 };
248
249 let redraw_start = std::time::Instant::now();
252 let last_redraw_start = state.last_redraw_start;
253 state.last_redraw_start = redraw_start;
256
257 let layout_start = std::time::Instant::now();
259 let new_vdom = cvkg_vdom::VDom::build(&self.view, rect);
260 let layout_end = std::time::Instant::now();
261
262 let state_flush_start = std::time::Instant::now();
264 if let Some(prev_vdom) = &mut state.vdom {
265 let patches = prev_vdom.diff(&new_vdom);
266 let mut nodes = Vec::new();
267 for patch in &patches {
268 if let cvkg_vdom::VDomPatch::Create(node) | cvkg_vdom::VDomPatch::Replace { node, .. } = patch {
269 nodes.push((accesskit::NodeId(node.id.0), node.to_accesskit_node()));
270 } else if let cvkg_vdom::VDomPatch::Update { id, .. } = patch
271 && let Some(node) = new_vdom.nodes.get(id) {
272 nodes.push((accesskit::NodeId(node.id.0), node.to_accesskit_node()));
273 }
274 }
275 if !nodes.is_empty() {
276 if let Some(adapter) = &mut state.accesskit_adapter {
277 adapter.update_if_active(|| accesskit::TreeUpdate {
278 nodes,
279 tree: None,
280 focus: accesskit::NodeId(1),
281 });
282 }
283 }
284 prev_vdom.apply_patches(patches);
285 } else {
286 state.vdom = Some(new_vdom);
287 }
288 let state_flush_end = std::time::Instant::now();
289
290 let draw_start = std::time::Instant::now();
292 let delta_time = redraw_start.duration_since(last_redraw_start).as_secs_f32();
293 let elapsed_time = redraw_start.duration_since(self.start_time).as_secs_f32();
294 let mut gpu = gpu_arc.lock().expect("GPU mutex poisoned during frame begin");
295 let encoder = gpu.begin_frame(id);
296 let mut renderer = NativeRenderer::new(
297 state.window.clone(),
298 gpu_arc.clone(),
299 delta_time,
300 elapsed_time,
301 self.berserker_mode,
302 self.rage,
303 );
304 drop(gpu);
308 self.view.render(&mut renderer, rect);
309 let draw_end = std::time::Instant::now();
310
311 let gpu_submit_start = std::time::Instant::now();
313 let mut gpu = gpu_arc.lock().expect("GPU mutex poisoned during frame submit");
314 gpu.render_frame();
315 gpu.end_frame(encoder);
316 let gpu_submit_end = std::time::Instant::now();
317
318 let mut telemetry = cvkg_core::TelemetryData::default();
323 telemetry.input_time_ms = redraw_start.duration_since(last_redraw_start).as_secs_f32() * 1000.0;
324 telemetry.layout_time_ms = layout_end.duration_since(layout_start).as_secs_f32() * 1000.0;
325 telemetry.state_flush_time_ms = state_flush_end.duration_since(state_flush_start).as_secs_f32() * 1000.0;
326 telemetry.draw_time_ms = draw_end.duration_since(draw_start).as_secs_f32() * 1000.0;
327 telemetry.gpu_submit_time_ms = gpu_submit_end.duration_since(gpu_submit_start).as_secs_f32() * 1000.0;
328
329 let frame_time_ms = gpu_submit_end.duration_since(redraw_start).as_secs_f32() * 1000.0;
331 telemetry.frame_time_ms = frame_time_ms;
332
333 state.frame_history.push_back(frame_time_ms);
335 if state.frame_history.len() > 100 {
336 state.frame_history.pop_front();
337 }
338
339 let mut sorted_frames: Vec<f32> = state.frame_history.iter().copied().collect();
340 sorted_frames.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
341
342 if !sorted_frames.is_empty() {
343 let p99_idx = (sorted_frames.len() as f32 * 0.99).floor() as usize;
344 telemetry.p99_frame_time_ms = sorted_frames[p99_idx.min(sorted_frames.len() - 1)];
345
346 let avg = sorted_frames.iter().sum::<f32>() / sorted_frames.len() as f32;
348 let variance = sorted_frames.iter().map(|f| (f - avg).powi(2)).sum::<f32>()
349 / sorted_frames.len() as f32;
350 telemetry.frame_jitter_ms = variance.sqrt();
351 }
352
353 telemetry.hardware_stall_detected = telemetry.frame_jitter_ms > 20.0;
359
360 state.frame_count += 1;
366
367 telemetry.berserker_rage = self.rage;
368 gpu.telemetry = telemetry;
369 }
370 WindowEvent::CursorEntered { .. } => {
371 log::info!("[Native] Cursor ENTERED window");
372 if let Some(vdom) = &state.vdom {
373 vdom.dispatch_event(cvkg_core::Event::PointerEnter);
374 }
375 state.window.request_redraw();
376 }
377 WindowEvent::CursorLeft { .. } => {
378 log::info!("[Native] Cursor LEFT window");
379 if let Some(vdom) = &state.vdom {
380 vdom.dispatch_event(cvkg_core::Event::PointerLeave);
381 }
382 state.window.request_redraw();
383 }
384 WindowEvent::CursorMoved { position, .. } => {
385 let scale = state.window.scale_factor();
386 let logical = position.to_logical::<f32>(scale);
387 log::info!("[Native] Cursor Moved: Physical={:?} Logical={:?} Scale={}", position, logical, scale);
388 state.cursor_pos = [logical.x, logical.y];
389 if let Some(vdom) = &state.vdom {
390 vdom.dispatch_event(cvkg_core::Event::PointerMove {
391 x: state.cursor_pos[0],
392 y: state.cursor_pos[1],
393 });
394 }
395 state.window.request_redraw();
397 }
398 WindowEvent::MouseInput { state: mouse_state, button, .. } => {
399 log::info!("[Native] MOUSE INPUT: {:?} button={:?} pos={:?}", mouse_state, button, state.cursor_pos);
400 if let Some(vdom) = &state.vdom {
401 let btn_id = match button {
402 winit::event::MouseButton::Left => 0,
403 winit::event::MouseButton::Right => 2,
404 winit::event::MouseButton::Middle => 1,
405 winit::event::MouseButton::Back => 3,
406 winit::event::MouseButton::Forward => 4,
407 winit::event::MouseButton::Other(id) => id as u32,
408 };
409
410 match mouse_state {
411 winit::event::ElementState::Pressed => {
412 log::info!("[Native] Dispatching PointerDown to VDOM");
413 vdom.dispatch_event(cvkg_core::Event::PointerDown {
414 x: state.cursor_pos[0],
415 y: state.cursor_pos[1],
416 button: btn_id,
417 });
418 }
419 winit::event::ElementState::Released => {
420 log::info!("[Native] Dispatching PointerUp to VDOM");
421 vdom.dispatch_event(cvkg_core::Event::PointerUp {
422 x: state.cursor_pos[0],
423 y: state.cursor_pos[1],
424 button: btn_id,
425 });
426 }
427 }
428 state.window.request_redraw();
429 } else {
430 log::warn!("[Native] Mouse input received but state.vdom is None!");
431 }
432 }
433 WindowEvent::KeyboardInput { event, .. } => {
434 if let Some(vdom) = &state.vdom
435 && let Some(cvkg_event) = convert_keyboard_event(event) {
436 vdom.dispatch_event(cvkg_event);
437 state.window.request_redraw();
438 }
439 }
440 WindowEvent::Ime(ime_event) => {
441 if let Some(vdom) = &state.vdom
442 && let Some(cvkg_event) = convert_ime_event(ime_event) {
443 vdom.dispatch_event(cvkg_event);
444 state.window.request_redraw();
445 }
446 }
447 _ => {}
448 }
449 }
450
451 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppEvent) {
452 let AppEvent::AccessibilityAction(request) = event;
453 let node_id = cvkg_vdom::NodeId(request.target.0);
454
455 let target_state = self.windows.values_mut().find(|s| {
460 s.vdom.as_ref().map_or(false, |v| v.nodes.contains_key(&node_id))
461 });
462
463 if let Some(state) = target_state
464 && let Some(vdom) = &state.vdom
465 && let Some(node) = vdom.nodes.get(&node_id)
466 && request.action == accesskit::Action::Click
467 {
468 let event = cvkg_core::Event::PointerClick {
469 x: node.layout.x + node.layout.width / 2.0,
470 y: node.layout.y + node.layout.height / 2.0,
471 button: 0, };
473 vdom.dispatch_event(event);
474 }
475 }
476
477 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
478 self.rage = (self.rage - 0.02).max(0.0);
480
481 let now = std::time::Instant::now();
483 let target_interval = std::time::Duration::from_millis(16);
484
485 if now.duration_since(self.last_frame_time) >= target_interval {
486 if self.rage > 0.01 {
487 log::debug!("[Native] Heartbeat ticking (rage: {})", self.rage);
489 }
490 self.last_frame_time = now;
491 for window_state in self.windows.values() {
492 window_state.window.request_redraw();
493 }
494 event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(now + target_interval));
495 } else {
496 event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(self.last_frame_time + target_interval));
497 }
498 }
499}
500
501impl cvkg_core::ElapsedTime for NativeRenderer {
502 fn delta_time(&self) -> f32 {
503 self.delta_time
504 }
505
506 fn elapsed_time(&self) -> f32 {
507 self.elapsed_time
508 }
509}
510
511impl cvkg_core::Renderer for NativeRenderer {
512
513 fn fill_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
514 self.gpu.lock().expect("GPU mutex poisoned: fill_rect").fill_rect(rect, color);
515 }
516 fn fill_rounded_rect(&mut self, rect: cvkg_core::Rect, radius: f32, color: [f32; 4]) {
517 self.gpu.lock().expect("GPU mutex poisoned: fill_rounded_rect").fill_rounded_rect(rect, radius, color);
518 }
519 fn fill_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
520 self.gpu.lock().expect("GPU mutex poisoned: fill_ellipse").fill_ellipse(rect, color);
521 }
522 fn stroke_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
523 self.gpu.lock().expect("GPU mutex poisoned: stroke_rect").stroke_rect(rect, color, stroke_width);
524 }
525 fn stroke_rounded_rect(
526 &mut self,
527 rect: cvkg_core::Rect,
528 radius: f32,
529 color: [f32; 4],
530 stroke_width: f32,
531 ) {
532 self.gpu.lock().expect("GPU mutex poisoned: stroke_rounded_rect").stroke_rounded_rect(rect, radius, color, stroke_width);
533 }
534 fn stroke_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
535 self.gpu.lock().expect("GPU mutex poisoned: stroke_ellipse").stroke_ellipse(rect, color, stroke_width);
536 }
537 fn draw_line(
538 &mut self,
539 x1: f32,
540 y1: f32,
541 x2: f32,
542 y2: f32,
543 color: [f32; 4],
544 stroke_width: f32,
545 ) {
546 self.gpu.lock().expect("GPU mutex poisoned: draw_line").draw_line(x1, y1, x2, y2, color, stroke_width);
547 }
548 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
549 self.gpu.lock().expect("GPU mutex poisoned: draw_text").draw_text(text, x, y, size, color);
550 }
551 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
552 self.gpu.lock().expect("GPU mutex poisoned: measure_text").measure_text(text, size)
553 }
554 fn draw_linear_gradient(&mut self, rect: cvkg_core::Rect, start_color: [f32; 4], end_color: [f32; 4], angle: f32) {
555 self.gpu.lock().expect("GPU mutex poisoned: draw_linear_gradient").draw_linear_gradient(rect, start_color, end_color, angle);
556 }
557 fn draw_radial_gradient(&mut self, rect: cvkg_core::Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
558 self.gpu.lock().expect("GPU mutex poisoned: draw_radial_gradient").draw_radial_gradient(rect, inner_color, outer_color);
559 }
560 fn draw_texture(&mut self, texture_id: u32, rect: cvkg_core::Rect) {
561 self.gpu.lock().expect("GPU mutex poisoned: draw_texture").draw_texture(texture_id, rect);
562 }
563 fn draw_image(&mut self, image_name: &str, rect: cvkg_core::Rect) {
564 self.gpu.lock().expect("GPU mutex poisoned: draw_image").draw_image(image_name, rect);
565 }
566 fn load_image(&mut self, name: &str, data: &[u8]) {
567 self.gpu.lock().expect("GPU mutex poisoned: load_image").load_image(name, data);
568 }
569 fn push_clip_rect(&mut self, rect: cvkg_core::Rect) {
570 self.gpu.lock().expect("GPU mutex poisoned: push_clip_rect").push_clip_rect(rect);
571 }
572 fn pop_clip_rect(&mut self) {
573 self.gpu.lock().expect("GPU mutex poisoned: pop_clip_rect").pop_clip_rect();
574 }
575 fn push_opacity(&mut self, opacity: f32) {
576 self.gpu.lock().expect("GPU mutex poisoned: push_opacity").push_opacity(opacity);
577 }
578 fn draw_3d_cube(&mut self, rect: cvkg_core::Rect, color: [f32; 4], rotation: [f32; 3]) {
579 self.gpu.lock().expect("GPU mutex poisoned: draw_3d_cube").draw_3d_cube(rect, color, rotation);
580 }
581 fn pop_opacity(&mut self) {
582 self.gpu.lock().expect("GPU mutex poisoned: pop_opacity").pop_opacity();
583 }
584 fn bifrost(&mut self, rect: cvkg_core::Rect, blur: f32, saturation: f32, opacity: f32) {
585 self.gpu.lock().expect("GPU mutex poisoned: bifrost").bifrost(rect, blur, saturation, opacity);
586 }
587 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
588 self.gpu.lock().expect("GPU mutex poisoned: push_mjolnir_slice").push_mjolnir_slice(angle, offset);
589 }
590 fn pop_mjolnir_slice(&mut self) {
591 self.gpu.lock().expect("GPU mutex poisoned: pop_mjolnir_slice").pop_mjolnir_slice();
592 }
593 fn mjolnir_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
594 self.gpu.lock().expect("GPU mutex poisoned: mjolnir_shatter").mjolnir_shatter(rect, pieces, force, color);
595 }
596 fn mjolnir_fluid_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
597 self.gpu.lock().expect("GPU mutex poisoned: mjolnir_fluid_shatter").mjolnir_fluid_shatter(rect, pieces, force, color);
598 }
599 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
600 self.gpu.lock().expect("GPU mutex poisoned: draw_mjolnir_bolt").draw_mjolnir_bolt(from, to, color);
601 }
602 fn gungnir(&mut self, rect: cvkg_core::Rect, color: [f32; 4], radius: f32, intensity: f32) {
603 self.gpu.lock().expect("GPU mutex poisoned: gungnir").gungnir(rect, color, radius, intensity);
604 }
605 fn mani_glow(&mut self, rect: cvkg_core::Rect, color: [f32; 4], radius: f32) {
606 self.gpu.lock().expect("GPU mutex poisoned: mani_glow").mani_glow(rect, color, radius);
607 }
608 fn register_handler(&mut self, event_type: &str, handler: std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>) {
609 self.gpu.lock().expect("GPU mutex poisoned: register_handler").register_handler(event_type, handler);
610 }
611 fn push_vnode(&mut self, rect: cvkg_core::Rect, name: &'static str) {
612 self.gpu.lock().expect("GPU mutex poisoned: push_vnode").push_vnode(rect, name);
613 }
614 fn pop_vnode(&mut self) {
615 self.gpu.lock().expect("GPU mutex poisoned: pop_vnode").pop_vnode();
616 }
617 fn set_z_index(&mut self, z: f32) {
621 self.gpu.lock().expect("GPU mutex poisoned: set_z_index").set_z_index(z);
622 }
623 fn get_z_index(&self) -> f32 {
624 self.gpu.lock().expect("GPU mutex poisoned: get_z_index").get_z_index()
625 }
626 fn register_shared_element(&mut self, id: &str, rect: cvkg_core::Rect) {
627 self.gpu.lock().expect("GPU mutex poisoned: register_shared_element").register_shared_element(id, rect);
628 }
629 fn load_svg(&mut self, name: &str, svg_data: &[u8]) {
630 self.gpu.lock().expect("GPU mutex poisoned: load_svg").load_svg(name, svg_data);
631 }
632 fn draw_svg(&mut self, name: &str, rect: cvkg_core::Rect) {
633 self.gpu.lock().expect("GPU mutex poisoned: draw_svg").draw_svg(name, rect, None, 0);
634 }
635 fn get_telemetry(&self) -> cvkg_core::TelemetryData {
636 self.gpu.lock().expect("GPU mutex poisoned: get_telemetry").telemetry.clone()
637 }
638 fn prewarm_vram(&mut self, assets: Vec<(String, Vec<u8>)>) {
639 self.gpu.lock().expect("GPU mutex poisoned: prewarm_vram").prewarm_vram(assets);
640 }
641 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
642 self.gpu.lock().expect("GPU mutex poisoned: push_transform").push_transform(translation, scale, rotation);
643 }
644 fn pop_transform(&mut self) {
645 self.gpu.lock().expect("GPU mutex poisoned: pop_transform").pop_transform();
646 }
647
648 fn set_berserker_mode(&mut self, state: cvkg_core::BerserkerMode) {
649 self.berserker_mode = state;
650
651 if state == cvkg_core::BerserkerMode::GodMode {
656 log::info!("ENTERING GOD MODE: Activating Berserker Determinism (High Priority)");
657 #[cfg(target_os = "linux")]
658 unsafe {
659 let _ = libc::setpriority(libc::PRIO_PROCESS, 0, -10);
660 }
661 } else {
662 #[cfg(target_os = "linux")]
663 unsafe {
664 let _ = libc::setpriority(libc::PRIO_PROCESS, 0, 0);
665 }
666 }
667
668 self.gpu.lock().expect("GPU mutex poisoned: set_berserker_mode").set_berserker_mode(state);
669 }
670
671 fn set_rage(&mut self, rage: f32) {
672 self.rage = rage;
673 self.gpu.lock().expect("GPU mutex poisoned: set_rage").set_rage(rage);
674 }
675
676 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
677 self.gpu.lock().expect("GPU mutex poisoned: memoize").memoize(id, data_hash, render_fn);
678 }
679 fn request_redraw(&mut self) {
680 self.window.request_redraw();
681 }
682
683 fn capture_png(&mut self) -> Vec<u8> {
693 log::info!("CAPTURING_FRAME: Initiating GPU readback...");
694 let gpu = self.gpu.lock().expect("GPU mutex poisoned: capture_png");
698 pollster::block_on(gpu.capture_frame()).unwrap_or_else(|e| {
699 log::error!("GPU frame capture failed: {}", e);
700 Vec::new() })
702 }
703
704 fn print(&mut self) {
705 log::info!("PRINT_BRIDGE: Spooling mission status to native printer...");
706 println!("[BRIDGE] PRINTER_READY // SPOOLING_DATA...");
709 }
710}
711
712fn convert_keyboard_event(event: winit::event::KeyEvent) -> Option<cvkg_core::Event> {
716 if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
717 let key_str = format!("{:?}", code);
718 if event.state == winit::event::ElementState::Pressed {
719 Some(cvkg_core::Event::KeyDown { key: key_str })
720 } else {
721 Some(cvkg_core::Event::KeyUp { key: key_str })
722 }
723 } else {
724 None
725 }
726}
727
728fn convert_ime_event(event: winit::event::Ime) -> Option<cvkg_core::Event> {
729 if let winit::event::Ime::Commit(string) = event {
730 Some(cvkg_core::Event::Ime(string))
731 } else {
732 None
733 }
734}
735
736struct ShieldWall {
739 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
740}
741
742impl accesskit::ActionHandler for ShieldWall {
743 fn do_action(&mut self, request: accesskit::ActionRequest) {
744 let _ = self
745 .proxy
746 .send_event(AppEvent::AccessibilityAction(request));
747 }
748}
749
750impl accesskit::ActivationHandler for ShieldWall {
751 fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
752 let mut root = accesskit::Node::new(accesskit::Role::Window);
753 root.set_label("CVKG Application");
754
755 let root_id = accesskit::NodeId(1);
756 Some(accesskit::TreeUpdate {
757 nodes: vec![(root_id, root)],
758 tree: Some(accesskit::Tree::new(root_id)),
759 focus: root_id,
760 })
761 }
762}
763
764impl accesskit::DeactivationHandler for ShieldWall {
765 fn deactivate_accessibility(&mut self) {}
766}
767
768type AssetCacheMap = std::collections::HashMap<String, cvkg_core::AssetState<std::sync::Arc<Vec<u8>>>>;
769
770pub struct NativeAssetManager {
776 cache: std::sync::Arc<arc_swap::ArcSwap<AssetCacheMap>>,
777}
778
779impl Default for NativeAssetManager {
780 fn default() -> Self {
781 Self::new()
782 }
783}
784
785impl NativeAssetManager {
786 pub fn new() -> Self {
788 Self {
789 cache: std::sync::Arc::new(arc_swap::ArcSwap::from_pointee(
790 std::collections::HashMap::new(),
791 )),
792 }
793 }
794}
795
796impl cvkg_core::AssetManager for NativeAssetManager {
797 fn load_image(&self, url: &str) -> cvkg_core::AssetState<std::sync::Arc<Vec<u8>>> {
812 if let Some(state) = self.cache.load().get(url) {
814 return state.clone();
815 }
816
817 let cache = self.cache.clone();
818 let key = url.to_string();
819
820 let mut we_inserted = false;
825 self.cache.rcu(|map| {
826 if map.contains_key(&key) {
827 (**map).clone()
829 } else {
830 we_inserted = true;
831 let mut m = (**map).clone();
832 m.insert(key.clone(), cvkg_core::AssetState::Loading);
833 m
834 }
835 });
836
837 if we_inserted {
840 let cache_inner = cache.clone();
841 let key_inner = key.clone();
842
843 std::thread::spawn(move || {
844 log::debug!("[Native] Asynchronously loading asset: {}", key_inner);
845 let result = match std::fs::read(&key_inner) {
846 Ok(data) => cvkg_core::AssetState::Ready(std::sync::Arc::new(data)),
847 Err(e) => cvkg_core::AssetState::Error(e.to_string()),
848 };
849
850 cache_inner.rcu(move |map| {
851 let mut m = (**map).clone();
852 m.insert(key_inner.clone(), result.clone());
853 m
854 });
855 });
856 }
857
858 cvkg_core::AssetState::Loading
859 }
860
861 fn preload_image(&self, url: &str) {
868 if self.cache.load().contains_key(url) {
870 return;
871 }
872
873 let cache = self.cache.clone();
874 let key = url.to_string();
875
876 let mut we_inserted = false;
877 self.cache.rcu(|map| {
878 if map.contains_key(&key) {
879 (**map).clone()
880 } else {
881 we_inserted = true;
882 let mut m = (**map).clone();
883 m.insert(key.clone(), cvkg_core::AssetState::Loading);
884 m
885 }
886 });
887
888 if we_inserted {
889 std::thread::spawn(move || {
890 log::debug!("[Native] Preloading asset: {}", key);
891 let result = match std::fs::read(&key) {
892 Ok(data) => cvkg_core::AssetState::Ready(std::sync::Arc::new(data)),
893 Err(e) => cvkg_core::AssetState::Error(e.to_string()),
894 };
895
896 cache.rcu(move |map| {
897 let mut m = (**map).clone();
898 m.insert(key.clone(), result.clone());
899 m
900 });
901 });
902 }
903 }
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909 use cvkg_core::AssetManager;
910 use std::io::Write;
911
912 #[test]
917 fn test_native_asset_manager_loading() {
918 let manager = NativeAssetManager::new();
919 let temp_path = std::env::temp_dir().join("cvkg_test_asset_loading.png");
920 let temp_file_path = temp_path.to_str().expect("temp path must be valid UTF-8");
921 let test_data = b"fake-image-data";
922
923 let mut file = std::fs::File::create(temp_file_path).unwrap();
925 file.write_all(test_data).unwrap();
926 drop(file);
927
928 let mut state = manager.load_image(temp_file_path);
930
931 let start = std::time::Instant::now();
933 while matches!(state, cvkg_core::AssetState::Loading) && start.elapsed().as_secs() < 5 {
934 std::thread::sleep(std::time::Duration::from_millis(10));
935 state = manager.load_image(temp_file_path);
936 }
937
938 if let cvkg_core::AssetState::Ready(data) = state {
939 assert_eq!(&*data, test_data);
940 } else {
941 let _ = std::fs::remove_file(temp_file_path);
942 panic!("Expected Ready state, got {:?}", state);
943 }
944
945 let state2 = manager.load_image(temp_file_path);
947 if let cvkg_core::AssetState::Ready(data) = state2 {
948 assert_eq!(&*data, test_data);
949 } else {
950 let _ = std::fs::remove_file(temp_file_path);
951 panic!("Expected Ready state (cached), got {:?}", state2);
952 }
953
954 let _ = std::fs::remove_file(temp_file_path);
955 }
956
957 #[test]
958 fn test_native_asset_manager_error() {
959 let manager = NativeAssetManager::new();
960 let path = "non_existent_file_cvkg_test.png";
961 let mut state = manager.load_image(path);
962
963 let start = std::time::Instant::now();
964 while matches!(state, cvkg_core::AssetState::Loading) && start.elapsed().as_secs() < 5 {
965 std::thread::sleep(std::time::Duration::from_millis(10));
966 state = manager.load_image(path);
967 }
968
969 if let cvkg_core::AssetState::Error(_) = state {
970 } else {
972 panic!("Expected Error state, got {:?}", state);
973 }
974 }
975
976 #[test]
977 fn test_event_conversion() {
978 let event = convert_mouse_event(winit::event::ElementState::Pressed, [10.0, 20.0], 0);
980 if let cvkg_core::Event::PointerDown { x, y, button } = event {
981 assert_eq!(x, 10.0);
982 assert_eq!(y, 20.0);
983 assert_eq!(button, 0);
984 } else {
985 panic!("Expected PointerDown");
986 }
987
988 let event = convert_ime_event(winit::event::Ime::Commit("hello".to_string()));
990 if let Some(cvkg_core::Event::Ime(s)) = event {
991 assert_eq!(s, "hello");
992 } else {
993 panic!("Expected Ime event");
994 }
995 }
996}
997
998fn load_icon() -> Option<winit::window::Icon> {
1002 let base = std::env::current_dir().unwrap_or_else(|e| {
1006 log::warn!("[Native] Failed to get current directory for icon search: {}", e);
1007 std::path::PathBuf::new()
1008 });
1009
1010 let mut candidates = vec![
1011 base.join("icon.png"),
1012 base.join("crates/ulfhednar/icons/icon.png"),
1013 base.join("ulfhednar/icons/icon.png"),
1014 base.join("crates/ulfhednar/assets/icon.png"),
1015 base.join("ulfhednar/assets/icon.png"),
1016 base.join("assets/icon.png"),
1017 ];
1018
1019 if let Ok(exe_path) = std::env::current_exe()
1021 && let Some(exe_dir) = exe_path.parent() {
1022 candidates.push(exe_dir.join("icons/icon.png"));
1023 candidates.push(exe_dir.join("assets/icon.png"));
1024 candidates.push(exe_dir.join("icon.png"));
1025 if let Some(parent) = exe_dir.parent() {
1026 candidates.push(parent.join("icons/icon.png"));
1027 candidates.push(parent.join("assets/icon.png"));
1028 candidates.push(parent.join("icon.png"));
1029 }
1030 }
1031
1032 for path in candidates {
1033 if !path.exists() {
1034 log::debug!("[Native] Icon candidate not found: {:?}", path);
1035 continue;
1036 }
1037
1038 match image::open(&path) {
1039 Ok(img) => {
1040 let rgba = img.to_rgba8();
1041 let (width, height) = rgba.dimensions();
1042 match winit::window::Icon::from_rgba(rgba.into_raw(), width, height) {
1043 Ok(icon) => {
1044 log::info!("[Native] Successfully loaded app icon from: {:?}", path);
1045 return Some(icon);
1046 }
1047 Err(e) => {
1048 log::warn!("[Native] Icon format error at {:?}: {}", path, e);
1049 }
1050 }
1051 }
1052 Err(e) => {
1053 log::warn!("[Native] Failed to open icon image at {:?}: {}", path, e);
1054 }
1055 }
1056 }
1057
1058 log::warn!("[Native] Failed to find icon.png in any search path (CWD: {:?})", base);
1059 None
1060}