Skip to main content

cvkg_render_native/
lib.rs

1//! # CVKG Agentic Development Guidelines (v1.2)
2//!
3//! All AI agents contributing to this crate MUST follow ALL seven rules:
4//!
5//! ── Karpathy Guidelines (1–4) ────────────────────────────────────────────
6//! 1. THINK FIRST     — State assumptions. Surface ambiguity. Push back on complexity.
7//! 2. STAY SIMPLE     — Minimum code. No speculative features. No unasked-for abstractions.
8//! 3. BE SURGICAL     — Touch only what's required. Own your orphans. Don't improve neighbors.
9//! 4. VERIFY GOALS    — Turn tasks into checkable criteria. Loop until they pass. Never commit broken.
10//!
11//! ── CVKG Extended Protocols (5–7) ────────────────────────────────────────
12//! 5. TRIPLE-PASS     — Read the target, its surrounding context, and its full call graph
13//                      at least THREE TIMES before making any edit or revision.
14//! 6. COMMENT ALL     — Every major pub fn, unsafe block, and non-trivial algorithm in
15//                      every .rs/.ts/.h/.wgsl file MUST have a descriptive doc comment.
16//                      Comments describe WHY and WHAT CONTRACT, not HOW mechanically.
17//! 7. MONITOR LOOPS   — Check every tool call / command for progress every 30 seconds.
18//                      After 3 consecutive identical failures, stop, write BLOCKED.md,
19//                      and move to unblocked work. Never silently accept a broken state.
20//!
21//! Sources:
22//   Karpathy: https://github.com/multica-ai/andrej-karpathy-skills
23//   CVKG Extended: Section 2 of the CVKG Design Specification
24
25//! Platform-native widget delegation using winit and AccessKit
26//!
27//! This crate provides platform-specific rendering backends for native desktop targets
28//  using winit for window/event handling and AccessKit for accessibility tree integration.
29
30use std::sync::Arc;
31use winit::{
32    application::ApplicationHandler,
33    event::WindowEvent,
34    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
35    window::{Window, WindowId},
36};
37
38
39/// Native renderer backend implementing the Renderer trait.
40/// It wraps a shared SurtrRenderer for high-performance GPU drawing.
41pub struct NativeRenderer {
42    gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>,
43    delta_time: f32,
44    elapsed_time: f32,
45}
46
47/// Custom events for the native application event loop
48#[derive(Debug)]
49enum AppEvent {
50    AccessibilityAction(accesskit::ActionRequest),
51}
52
53impl NativeRenderer {
54    /// Create a new NativeRenderer (internal use by App)
55    fn new(_window: Arc<Window>, gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>, delta_time: f32, elapsed_time: f32) -> Self {
56        Self { gpu, delta_time, elapsed_time }
57    }
58
59
60    /// Start the CVKG native application with the given view.
61    /// This is the main entry point for desktop applications.
62    pub fn run<V: cvkg_core::View + 'static>(view: V) {
63        let event_loop = EventLoop::<AppEvent>::with_user_event()
64            .build()
65            .expect("Failed to create event loop");
66        event_loop.set_control_flow(ControlFlow::Poll);
67
68        let mut app = App {
69            view,
70            windows: std::collections::HashMap::new(),
71            gpu: None,
72            asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
73            proxy: event_loop.create_proxy(),
74            start_time: std::time::Instant::now(),
75        };
76
77        event_loop.run_app(&mut app).expect("Event loop error");
78    }
79}
80
81struct WindowState {
82    window: Arc<Window>,
83    accesskit_adapter: Option<accesskit_winit::Adapter>,
84    vdom: Option<cvkg_vdom::VDom>,
85    cursor_pos: [f32; 2],
86    /// The instant the last redraw finished, used for measuring inter-frame timing.
87    last_redraw_start: std::time::Instant,
88}
89
90struct App<V: cvkg_core::View> {
91    view: V,
92    windows: std::collections::HashMap<WindowId, WindowState>,
93    gpu: Option<Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>>,
94    asset_manager: std::sync::Arc<NativeAssetManager>,
95    proxy: winit::event_loop::EventLoopProxy<AppEvent>,
96    start_time: std::time::Instant,
97}
98
99impl<V: cvkg_core::View + 'static> ApplicationHandler<AppEvent> for App<V> {
100    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
101        if self.gpu.is_none() {
102            let window_attrs = Window::default_attributes()
103                .with_title("CVKG Forge")
104                .with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0));
105
106            let window = Arc::new(
107                event_loop
108                    .create_window(window_attrs)
109                    .expect("Failed to create window"),
110            );
111            window.set_ime_allowed(true);
112
113            let adapter = accesskit_winit::Adapter::with_direct_handlers(
114                event_loop,
115                &window,
116                ShieldWall { proxy: self.proxy.clone() },
117                ShieldWall { proxy: self.proxy.clone() },
118                ShieldWall { proxy: self.proxy.clone() },
119            );
120
121            let rt = tokio::runtime::Runtime::new().unwrap();
122            let gpu = rt.block_on(cvkg_render_gpu::SurtrRenderer::forge(window.clone()));
123            let gpu = Arc::new(std::sync::Mutex::new(gpu));
124            self.gpu = Some(gpu);
125
126            self.windows.insert(window.id(), WindowState {
127                window,
128                accesskit_adapter: Some(adapter),
129                vdom: Some(cvkg_vdom::VDom::new()),
130                cursor_pos: [0.0, 0.0],
131                last_redraw_start: std::time::Instant::now(),
132            });
133
134            cvkg_core::env::insert::<cvkg_core::AssetKey>(self.asset_manager.clone());
135        }
136    }
137
138    fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
139        let gpu_arc = if let Some(g) = &self.gpu { g.clone() } else { return };
140        let state = if let Some(s) = self.windows.get_mut(&id) { s } else { return };
141
142        match event {
143            WindowEvent::CloseRequested => {
144                self.windows.remove(&id);
145                if self.windows.is_empty() {
146                    event_loop.exit();
147                }
148            }
149            WindowEvent::Resized(physical_size) => {
150                gpu_arc.lock().unwrap().resize(
151                    id,
152                    physical_size.width,
153                    physical_size.height,
154                    state.window.scale_factor() as f32,
155                );
156                state.window.request_redraw();
157            }
158            WindowEvent::RedrawRequested => {
159                let size = state.window.inner_size();
160                let scale = state.window.scale_factor();
161                let logical_size = size.to_logical::<f32>(scale);
162
163                let rect = cvkg_core::Rect {
164                    x: 0.0,
165                    y: 0.0,
166                    width: logical_size.width,
167                    height: logical_size.height,
168                };
169
170                // Start timing for this redraw
171                let redraw_start = std::time::Instant::now();
172                
173                // Build new vdom and diff (layout pass)
174                let layout_start = std::time::Instant::now();
175                let new_vdom = cvkg_vdom::VDom::build(&self.view, rect);
176                let layout_end = std::time::Instant::now();
177
178                // Apply patches
179                let state_flush_start = std::time::Instant::now();
180                if let Some(prev_vdom) = &mut state.vdom {
181                    let patches = prev_vdom.diff(&new_vdom);
182                    if let Some(adapter) = &mut state.accesskit_adapter {
183                        let mut nodes = Vec::new();
184                        for patch in &patches {
185                            if let cvkg_vdom::VDomPatch::Create(node) | cvkg_vdom::VDomPatch::Replace { node, .. } = patch {
186                                nodes.push((accesskit::NodeId(node.id.0), node.to_accesskit_node()));
187                            } else if let cvkg_vdom::VDomPatch::Update { id, .. } = patch
188                                && let Some(node) = new_vdom.nodes.get(id) {
189                                nodes.push((accesskit::NodeId(node.id.0), node.to_accesskit_node()));
190                            }
191                        }
192                        if !nodes.is_empty() {
193                            adapter.update_if_active(|| accesskit::TreeUpdate {
194                                nodes,
195                                tree: None,
196                                focus: accesskit::NodeId(1),
197                            });
198                        }
199                    }
200                    prev_vdom.apply_patches(patches);
201                } else {
202                    state.vdom = Some(new_vdom);
203                }
204                let state_flush_end = std::time::Instant::now();
205
206                // GPU rendering
207                let draw_start = std::time::Instant::now();
208                let delta_time = redraw_start.duration_since(state.last_redraw_start).as_secs_f32();
209                let elapsed_time = redraw_start.duration_since(self.start_time).as_secs_f32();
210                let mut gpu = gpu_arc.lock().unwrap();
211                let encoder = gpu.begin_frame(id);
212                let mut renderer = NativeRenderer::new(state.window.clone(), gpu_arc.clone(), delta_time, elapsed_time);
213                self.view.render(&mut renderer, rect);
214                let draw_end = std::time::Instant::now();
215
216                // Submission
217                let gpu_submit_start = std::time::Instant::now();
218                gpu.end_frame(encoder);
219                let gpu_submit_end = std::time::Instant::now();
220
221                // Update telemetry
222                let mut telemetry = gpu.telemetry.clone();
223                // input_time_ms uses the previous frame's completion to this frame's start as a proxy
224                telemetry.input_time_ms = redraw_start.duration_since(state.last_redraw_start).as_secs_f32() * 1000.0;
225                telemetry.layout_time_ms = layout_end.duration_since(layout_start).as_secs_f32() * 1000.0;
226                telemetry.state_flush_time_ms = state_flush_end.duration_since(state_flush_start).as_secs_f32() * 1000.0;
227                telemetry.draw_time_ms = draw_end.duration_since(draw_start).as_secs_f32() * 1000.0;
228                telemetry.gpu_submit_time_ms = gpu_submit_end.duration_since(gpu_submit_start).as_secs_f32() * 1000.0;
229                
230                // Total frame time
231                telemetry.frame_time_ms = gpu_submit_end.duration_since(redraw_start).as_secs_f32() * 1000.0;
232                
233                gpu.telemetry = telemetry;
234                state.last_redraw_start = gpu_submit_end;
235            }
236            WindowEvent::CursorMoved { position, .. } => {
237                let scale = state.window.scale_factor();
238                let logical = position.to_logical::<f32>(scale);
239                state.cursor_pos = [logical.x, logical.y];
240                if let Some(vdom) = &state.vdom {
241                    vdom.dispatch_event(cvkg_core::Event::PointerMove {
242                        x: state.cursor_pos[0],
243                        y: state.cursor_pos[1],
244                    });
245                }
246            }
247            WindowEvent::MouseInput { state: mouse_state, .. } => {
248                if let Some(vdom) = &state.vdom {
249                    let event = convert_mouse_event(mouse_state, state.cursor_pos);
250                    vdom.dispatch_event(event);
251                }
252            }
253            WindowEvent::KeyboardInput { event, .. } => {
254                if let Some(vdom) = &state.vdom
255                    && let Some(cvkg_event) = convert_keyboard_event(event) {
256                        vdom.dispatch_event(cvkg_event);
257                }
258            }
259            WindowEvent::Ime(ime_event) => {
260                if let Some(vdom) = &state.vdom
261                    && let Some(cvkg_event) = convert_ime_event(ime_event) {
262                        vdom.dispatch_event(cvkg_event);
263                }
264            }
265            _ => {}
266        }
267    }
268
269    fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppEvent) {
270        let AppEvent::AccessibilityAction(request) = event;
271            let node_id = cvkg_vdom::NodeId(request.target.0);
272            // For accessibility, we'll route to the first window for now
273            if let Some(state) = self.windows.values_mut().next()
274                && let Some(vdom) = &state.vdom
275                && let Some(node) = vdom.nodes.get(&node_id)
276                && request.action == accesskit::Action::Click {
277                    let event = cvkg_core::Event::PointerClick {
278                        x: node.layout.x + node.layout.width / 2.0,
279                        y: node.layout.y + node.layout.height / 2.0,
280                    };
281                    vdom.dispatch_event(event);
282            }
283    }
284
285    fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
286        for state in self.windows.values() {
287            state.window.request_redraw();
288        }
289    }
290}
291
292impl cvkg_core::ElapsedTime for NativeRenderer {
293    fn delta_time(&self) -> f32 {
294        self.delta_time
295    }
296
297    fn elapsed_time(&self) -> f32 {
298        self.elapsed_time
299    }
300}
301
302impl cvkg_core::Renderer for NativeRenderer {
303
304    fn fill_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
305        self.gpu.lock().unwrap().fill_rect(rect, color);
306    }
307    fn fill_rounded_rect(&mut self, rect: cvkg_core::Rect, radius: f32, color: [f32; 4]) {
308        self.gpu.lock().unwrap().fill_rounded_rect(rect, radius, color);
309    }
310    fn fill_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
311        self.gpu.lock().unwrap().fill_ellipse(rect, color);
312    }
313    fn stroke_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
314        self.gpu.lock().unwrap().stroke_rect(rect, color, stroke_width);
315    }
316    fn stroke_rounded_rect(
317        &mut self,
318        rect: cvkg_core::Rect,
319        radius: f32,
320        color: [f32; 4],
321        stroke_width: f32,
322    ) {
323        self.gpu.lock().unwrap().stroke_rounded_rect(rect, radius, color, stroke_width);
324    }
325    fn stroke_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
326        self.gpu.lock().unwrap().stroke_ellipse(rect, color, stroke_width);
327    }
328    fn draw_line(
329        &mut self,
330        x1: f32,
331        y1: f32,
332        x2: f32,
333        y2: f32,
334        color: [f32; 4],
335        stroke_width: f32,
336    ) {
337        self.gpu.lock().unwrap().draw_line(x1, y1, x2, y2, color, stroke_width);
338    }
339    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
340        self.gpu.lock().unwrap().draw_text(text, x, y, size, color);
341    }
342    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
343        self.gpu.lock().unwrap().measure_text(text, size)
344    }
345    fn draw_texture(&mut self, texture_id: u32, rect: cvkg_core::Rect) {
346        self.gpu.lock().unwrap().draw_texture(texture_id, rect);
347    }
348    fn draw_image(&mut self, image_name: &str, rect: cvkg_core::Rect) {
349        self.gpu.lock().unwrap().draw_image(image_name, rect);
350    }
351    fn load_image(&mut self, name: &str, data: &[u8]) {
352        self.gpu.lock().unwrap().load_image(name, data);
353    }
354    fn push_clip_rect(&mut self, rect: cvkg_core::Rect) {
355        self.gpu.lock().unwrap().push_clip_rect(rect);
356    }
357    fn pop_clip_rect(&mut self) {
358        self.gpu.lock().unwrap().pop_clip_rect();
359    }
360    fn push_opacity(&mut self, opacity: f32) {
361        self.gpu.lock().unwrap().push_opacity(opacity);
362    }
363    fn pop_opacity(&mut self) {
364        self.gpu.lock().unwrap().pop_opacity();
365    }
366    fn bifrost(&mut self, rect: cvkg_core::Rect, blur: f32, saturation: f32, opacity: f32) {
367        self.gpu.lock().unwrap().bifrost(rect, blur, saturation, opacity);
368    }
369    fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
370        self.gpu.lock().unwrap().push_mjolnir_slice(angle, offset);
371    }
372    fn pop_mjolnir_slice(&mut self) {
373        self.gpu.lock().unwrap().pop_mjolnir_slice();
374    }
375    fn mjolnir_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
376        self.gpu.lock().unwrap().mjolnir_shatter(rect, pieces, force, color);
377    }
378    fn mjolnir_fluid_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
379        self.gpu.lock().unwrap().mjolnir_fluid_shatter(rect, pieces, force, color);
380    }
381    fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
382        self.gpu.lock().unwrap().draw_mjolnir_bolt(from, to, color);
383    }
384    fn register_shared_element(&mut self, id: &str, rect: cvkg_core::Rect) {
385        self.gpu.lock().unwrap().register_shared_element(id, rect);
386    }
387    fn set_z_index(&mut self, z: f32) {
388        self.gpu.lock().unwrap().set_z_index(z);
389    }
390    fn get_z_index(&self) -> f32 {
391        self.gpu.lock().unwrap().get_z_index()
392    }
393    fn load_svg(&mut self, name: &str, svg_data: &[u8]) {
394        self.gpu.lock().unwrap().load_svg(name, svg_data);
395    }
396    fn draw_svg(&mut self, name: &str, rect: cvkg_core::Rect) {
397        self.gpu.lock().unwrap().draw_svg(name, rect, None, 0);
398    }
399    fn get_telemetry(&self) -> cvkg_core::TelemetryData {
400        self.gpu.lock().unwrap().telemetry.clone()
401    }
402
403    fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
404        self.gpu.lock().unwrap().push_transform(translation, scale, rotation);
405    }
406
407    fn pop_transform(&mut self) {
408        self.gpu.lock().unwrap().pop_transform();
409    }
410}
411
412// ── Event Conversion Helpers ───────────────────────────────────────────
413
414fn convert_mouse_event(state: winit::event::ElementState, pos: [f32; 2]) -> cvkg_core::Event {
415    match state {
416        winit::event::ElementState::Pressed => cvkg_core::Event::PointerDown { x: pos[0], y: pos[1] },
417        winit::event::ElementState::Released => cvkg_core::Event::PointerUp { x: pos[0], y: pos[1] },
418    }
419}
420
421fn convert_keyboard_event(event: winit::event::KeyEvent) -> Option<cvkg_core::Event> {
422    if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
423        let key_str = format!("{:?}", code);
424        if event.state == winit::event::ElementState::Pressed {
425            Some(cvkg_core::Event::KeyDown { key: key_str })
426        } else {
427            Some(cvkg_core::Event::KeyUp { key: key_str })
428        }
429    } else {
430        None
431    }
432}
433
434fn convert_ime_event(event: winit::event::Ime) -> Option<cvkg_core::Event> {
435    if let winit::event::Ime::Commit(string) = event {
436        Some(cvkg_core::Event::Ime(string))
437    } else {
438        None
439    }
440}
441
442// Platform-specific implementations for macOS, Windows, and Linux are handled by winit and AccessKit.
443
444struct ShieldWall {
445    proxy: winit::event_loop::EventLoopProxy<AppEvent>,
446}
447
448impl accesskit::ActionHandler for ShieldWall {
449    fn do_action(&mut self, request: accesskit::ActionRequest) {
450        let _ = self
451            .proxy
452            .send_event(AppEvent::AccessibilityAction(request));
453    }
454}
455
456impl accesskit::ActivationHandler for ShieldWall {
457    fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
458        let mut root = accesskit::Node::new(accesskit::Role::Window);
459        root.set_label("CVKG Application");
460
461        let root_id = accesskit::NodeId(1);
462        Some(accesskit::TreeUpdate {
463            nodes: vec![(root_id, root)],
464            tree: Some(accesskit::Tree::new(root_id)),
465            focus: root_id,
466        })
467    }
468}
469
470impl accesskit::DeactivationHandler for ShieldWall {
471    fn deactivate_accessibility(&mut self) {}
472}
473
474/// A concrete AssetManager for native desktop targets that loads from the local filesystem.
475///
476/// The cache is read on every render frame (lock-free via `ArcSwap::load()`) but written
477/// at most once per URL after disk I/O completes. `rcu()` atomically inserts the result
478/// without blocking concurrent render-loop readers.
479pub struct NativeAssetManager {
480    cache: std::sync::Arc<
481        arc_swap::ArcSwap<
482            std::collections::HashMap<String, cvkg_core::AssetState<std::sync::Arc<Vec<u8>>>>,
483        >,
484    >,
485}
486
487impl Default for NativeAssetManager {
488    fn default() -> Self {
489        Self::new()
490    }
491}
492
493impl NativeAssetManager {
494    /// Create a new, empty NativeAssetManager.
495    pub fn new() -> Self {
496        Self {
497            cache: std::sync::Arc::new(arc_swap::ArcSwap::from_pointee(
498                std::collections::HashMap::new(),
499            )),
500        }
501    }
502}
503
504impl cvkg_core::AssetManager for NativeAssetManager {
505    /// Return the cached asset state for `url`.
506    ///
507    /// Fast path: lock-free snapshot read via `ArcSwap::load()`.
508    /// Slow path (cache miss): perform filesystem I/O, then publish the result
509    /// with `rcu()` — no lock is held while reading the disk.
510    fn load_image(&self, url: &str) -> cvkg_core::AssetState<std::sync::Arc<Vec<u8>>> {
511        // Fast path: lock-free read from current cache snapshot
512        if let Some(state) = self.cache.load().get(url) {
513            return state.clone();
514        }
515
516        // Slow path: disk I/O, then atomic rcu insert
517        let result = match std::fs::read(url) {
518            Ok(data) => cvkg_core::AssetState::Ready(std::sync::Arc::new(data)),
519            Err(e) => cvkg_core::AssetState::Error(e.to_string()),
520        };
521        let result_clone = result.clone();
522        let key = url.to_string();
523        self.cache.rcu(move |map| {
524            let mut m = (**map).clone();
525            m.insert(key.clone(), result_clone.clone());
526            m
527        });
528        result
529    }
530
531    fn preload_image(&self, _url: &str) {
532        // Async preloading could be wired to a background thread here
533    }
534}
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539    use cvkg_core::AssetManager;
540    use std::io::Write;
541
542    #[test]
543    fn test_native_asset_manager_loading() {
544        let manager = NativeAssetManager::new();
545        let temp_file_path = "test_asset.png";
546        let test_data = b"fake-image-data";
547        
548        // Create a temporary file
549        let mut file = std::fs::File::create(temp_file_path).unwrap();
550        file.write_all(test_data).unwrap();
551        
552        // Test loading
553        let state = manager.load_image(temp_file_path);
554        if let cvkg_core::AssetState::Ready(data) = state {
555            assert_eq!(&*data, test_data);
556        } else {
557            panic!("Expected Ready state");
558        }
559        
560        // Test caching (fast path)
561        let state2 = manager.load_image(temp_file_path);
562        if let cvkg_core::AssetState::Ready(data) = state2 {
563            assert_eq!(&*data, test_data);
564        } else {
565            panic!("Expected Ready state (cached)");
566        }
567        
568        // Cleanup
569        let _ = std::fs::remove_file(temp_file_path);
570    }
571
572    #[test]
573    fn test_native_asset_manager_error() {
574        let manager = NativeAssetManager::new();
575        let state = manager.load_image("non_existent_file.png");
576        if let cvkg_core::AssetState::Error(_) = state {
577            // Success
578        } else {
579            panic!("Expected Error state");
580        }
581    }
582
583    #[test]
584    fn test_event_conversion() {
585        // Mouse event
586        let event = convert_mouse_event(winit::event::ElementState::Pressed, [10.0, 20.0]);
587        if let cvkg_core::Event::PointerDown { x, y } = event {
588            assert_eq!(x, 10.0);
589            assert_eq!(y, 20.0);
590        } else {
591            panic!("Expected PointerDown");
592        }
593
594        // IME event
595        let event = convert_ime_event(winit::event::Ime::Commit("hello".to_string()));
596        if let Some(cvkg_core::Event::Ime(s)) = event {
597            assert_eq!(s, "hello");
598        } else {
599            panic!("Expected Ime event");
600        }
601    }
602}