Просмотр исходного кода

wallet: UI drawing responds to property changes

darkfi 2 лет назад
Родитель
Сommit
8f88b3a77f
5 измененных файлов с 274 добавлено и 238 удалено
  1. 8 0
      bin/darkwallet/Makefile
  2. 196 65
      bin/darkwallet/src/app.rs
  3. 31 26
      bin/darkwallet/src/gfx2.rs
  4. 37 146
      bin/darkwallet/src/main.rs
  5. 2 1
      bin/darkwallet/src/prop/mod.rs

+ 8 - 0
bin/darkwallet/Makefile

@@ -0,0 +1,8 @@
+android:
+	docker run -v $(shell pwd):/root/dw -v /tmp/miniquad:/tmp/miniquad -w /root/dw -t apk cargo quad-apk build
+	adb uninstall rust.darkwallet
+	adb install target/android-artifacts/debug/apk/darkwallet.apk
+
+clean:
+	docker run -v $(shell pwd):/root/dw -w /root/dw -t apk rm -fr target/
+

+ 196 - 65
bin/darkwallet/src/app.rs

@@ -12,7 +12,7 @@ use crate::{
     error::{Error, Result},
     expr::{Op, SExprMachine, SExprVal},
     gfx::Rectangle,
-    gfx2::{DrawCall, DrawInstruction, DrawMesh, GraphicsEvent, RenderApiPtr, Vertex},
+    gfx2::{self, DrawCall, DrawInstruction, DrawMesh, GraphicsEvent, RenderApiPtr, Vertex},
     prop::{
         Property, PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyStr,
         PropertySubType, PropertyType, PropertyUint32,
@@ -25,9 +25,80 @@ use crate::{
 };
 
 trait Stoppable {
-    async fn stop(self);
+    async fn stop(&self);
 }
 
+pub type AsyncRuntimePtr = Arc<AsyncRuntime>;
+
+pub struct AsyncRuntime {
+    signal: smol::channel::Sender<()>,
+    shutdown: smol::channel::Receiver<()>,
+    exec_threadpool: std::sync::Mutex<Option<thread::JoinHandle<()>>>,
+    ex: Arc<smol::Executor<'static>>,
+    tasks: std::sync::Mutex<Vec<smol::Task<()>>>,
+}
+
+impl AsyncRuntime {
+    pub fn new(ex: Arc<smol::Executor<'static>>) -> Self {
+        let (signal, shutdown) = smol::channel::unbounded::<()>();
+
+        Self {
+            signal,
+            shutdown,
+            exec_threadpool: std::sync::Mutex::new(None),
+            ex,
+            tasks: std::sync::Mutex::new(vec![]),
+        }
+    }
+
+    pub fn start(&self) {
+        let n_threads = std::thread::available_parallelism().unwrap().get();
+        let shutdown = self.shutdown.clone();
+        let ex = self.ex.clone();
+        let exec_threadpool = thread::spawn(move || {
+            easy_parallel::Parallel::new()
+                // N executor threads
+                .each(0..n_threads, |_| smol::future::block_on(ex.run(shutdown.recv())))
+                .run();
+        });
+        *self.exec_threadpool.lock().unwrap() = Some(exec_threadpool);
+        debug!(target: "async_runtime", "Started runtime");
+    }
+
+    pub fn push_task(&self, task: smol::Task<()>) {
+        self.tasks.lock().unwrap().push(task);
+    }
+
+    pub fn stop(&self) {
+        // Go through event graph and call stop on everything
+        // Depth first
+        debug!(target: "app", "Stopping app...");
+
+        let tasks = std::mem::take(&mut *self.tasks.lock().unwrap());
+        // Close all tasks
+        smol::future::block_on(async {
+            // Perform cleanup code
+            // If not finished in certain amount of time, then just exit
+
+            let mut futures = FuturesUnordered::new();
+            for task in tasks {
+                futures.push(task.cancel());
+            }
+            let _: Vec<_> = futures.collect().await;
+        });
+
+        if !self.signal.close() {
+            error!(target: "app", "exec threadpool was already shutdown");
+        }
+        let exec_threadpool = std::mem::replace(&mut *self.exec_threadpool.lock().unwrap(), None);
+        let exec_threadpool = exec_threadpool.expect("threadpool wasnt started");
+        exec_threadpool.join();
+        debug!(target: "app", "Stopped app");
+    }
+}
+
+pub type AppPtr = Arc<App>;
+
 pub struct App {
     sg: SceneGraphPtr2,
     ex: Arc<smol::Executor<'static>>,
@@ -42,7 +113,6 @@ impl App {
         render_api: RenderApiPtr,
         event_pub: PublisherPtr<GraphicsEvent>,
     ) -> Arc<Self> {
-        debug!("App::new()");
         Arc::new(Self { sg, ex, render_api, event_pub })
     }
 
@@ -97,6 +167,25 @@ impl App {
         self.trigger_redraw().await;
     }
 
+    pub async fn stop(&self) {
+        let sg = self.sg.lock().await;
+        let window_id = sg.lookup_node("/window").unwrap().id;
+        self.stop_node(&sg, window_id).await;
+    }
+
+    async fn stop_node(&self, sg: &SceneGraph, node_id: SceneNodeId) {
+        let node = sg.get_node(node_id).unwrap();
+        for child_inf in node.get_children2() {
+            self.stop_node(sg, child_inf.id);
+        }
+        match &node.pimpl {
+            Pimpl::Window(win) => win.stop().await,
+            Pimpl::RenderLayer(layer) => layer.stop().await,
+            Pimpl::Mesh(mesh) => mesh.stop().await,
+            _ => panic!("unhandled pimpl type"),
+        };
+    }
+
     async fn make_me_a_schema_plox(&self) {
         // Create a layer called view
         let mut sg = self.sg.lock().await;
@@ -169,12 +258,6 @@ impl App {
             _ => panic!("wrong pimpl"),
         }
     }
-
-    pub async fn stop(&self) {
-        // Go through event graph and call stop on everything
-        // Depth first
-        debug!("Stopping app...");
-    }
 }
 
 fn print_type_of<T>(_: &T) {
@@ -199,7 +282,7 @@ impl Window {
         render_api: RenderApiPtr,
         event_pub: PublisherPtr<GraphicsEvent>,
     ) -> Pimpl {
-        debug!("Window::new()");
+        debug!(target: "app", "Window::new()");
 
         let screen_size_prop = {
             let sg = sg.lock().await;
@@ -207,27 +290,6 @@ impl Window {
             node.get_property("screen_size").unwrap()
         };
 
-        // Start a task monitoring for window resize events
-        // which updates screen_size
-        let ev_sub = event_pub.subscribe();
-        let screen_size_prop2 = screen_size_prop.clone();
-        let resize_task = ex.spawn(async move {
-            loop {
-                let Ok(ev) = ev_sub.receive().await else {
-                    debug!("Event relayer closed");
-                    break
-                };
-                let (w, h) = match ev {
-                    GraphicsEvent::Resize((w, h)) => (w, h),
-                    _ => continue,
-                };
-
-                // Now update the properties
-                screen_size_prop2.set_f32(0, w);
-                screen_size_prop2.set_f32(1, h);
-            }
-        });
-
         // Monitor for changes to screen_size or scale properties
         // If so then trigger draw
         let scale_sub = {
@@ -236,31 +298,55 @@ impl Window {
             let prop = node.get_property("scale").unwrap();
             prop.subscribe_modify()
         };
-        let screen_size_sub = screen_size_prop.subscribe_modify();
 
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
+            // Start a task monitoring for window resize events
+            // which updates screen_size
+            let ev_sub = event_pub.subscribe();
+            let screen_size_prop2 = screen_size_prop.clone();
+            let me2 = me.clone();
+            let sg2 = sg.clone();
+            let resize_task = ex.spawn(async move {
+                loop {
+                    let Ok(ev) = ev_sub.receive().await else {
+                        debug!(target: "app", "Event relayer closed");
+                        break
+                    };
+                    let (w, h) = match ev {
+                        GraphicsEvent::Resize((w, h)) => (w, h),
+                        _ => continue,
+                    };
+
+                    debug!(target: "app", "Window resized ({w}, {h})");
+                    // Now update the properties
+                    screen_size_prop2.set_f32(0, w);
+                    screen_size_prop2.set_f32(1, h);
+
+                    let Some(self_) = me2.upgrade() else {
+                        // Should not happen
+                        panic!("self destroyed before modify_task was stopped!");
+                    };
+
+                    let sg = sg2.lock().await;
+                    self_.draw(&sg).await;
+                }
+            });
+
             // Modify task needs a Weak<Self>
             let me2 = me.clone();
             let modify_task = ex.spawn(async move {
                 loop {
-                    let mut futures = FuturesUnordered::new();
-                    futures.push(scale_sub.receive());
-                    futures.push(screen_size_sub.receive());
-
-                    while let Some(ev) = futures.next().await {
-                        let Ok(_) = ev else {
-                            debug!("prop sub closed");
-                            break
-                        };
-
-                        let Some(self_) = me2.upgrade() else {
-                            // Should not happen
-                            panic!("self destroyed before modify_task was stopped!");
-                        };
-
-                        let sg = sg.lock().await;
-                        self_.draw(&sg).await;
-                    }
+                    let _ = scale_sub.receive().await;
+                    debug!(target: "app", "Window scale modified");
+
+                    let Some(self_) = me2.upgrade() else {
+                        // Should not happen
+                        panic!("self destroyed before modify_task was stopped!");
+                    };
+
+                    debug!(target: "app", "window property modified");
+                    let sg = sg.lock().await;
+                    self_.draw(&sg).await;
                 }
             });
 
@@ -271,7 +357,7 @@ impl Window {
     }
 
     async fn draw(&self, sg: &SceneGraph) {
-        debug!("Window::draw()");
+        debug!(target: "app", "Window::draw()");
         // SceneGraph should remain locked for the entire draw
         let self_node = sg.get_node(self.node_id).unwrap();
 
@@ -284,12 +370,12 @@ impl Window {
         let mut child_calls = vec![];
         for child_inf in self_node.get_children2() {
             let node = sg.get_node(child_inf.id).unwrap();
-            debug!("Window::draw() calling draw() for node '{}':{}", node.name, node.id);
+            debug!(target: "app", "Window::draw() calling draw() for node '{}':{}", node.name, node.id);
 
             let dcs = match &node.pimpl {
                 Pimpl::RenderLayer(layer) => layer.draw(sg, &parent_rect).await,
                 _ => {
-                    error!("unhandled pimpl type");
+                    error!(target: "app", "unhandled pimpl type");
                     continue
                 }
             };
@@ -300,7 +386,7 @@ impl Window {
 
         let root_dc = DrawCall { instrs: vec![], dcs: child_calls };
         draw_calls.push((0, root_dc));
-        println!("{:?}", draw_calls);
+        //debug!("  => {:?}", draw_calls);
 
         self.render_api.replace_draw_calls(draw_calls).await;
         debug!("Window::draw() - replaced draw call");
@@ -309,9 +395,7 @@ impl Window {
 
 // Nodes should be stopped before being removed
 impl Stoppable for Window {
-    async fn stop(self) {
-        self.resize_task.cancel().await;
-    }
+    async fn stop(&self) {}
 }
 
 pub type RenderLayerPtr = Arc<RenderLayer>;
@@ -324,6 +408,8 @@ pub struct RenderLayer {
 
     is_visible: PropertyBool,
     rect: PropertyPtr,
+
+    parent_rect: Mutex<Rectangle<f32>>,
 }
 
 impl RenderLayer {
@@ -336,7 +422,38 @@ impl RenderLayer {
             PropertyBool::wrap(node, "is_visible", 0).expect("RenderLayer::is_visible");
         let rect = node.get_property("rect").expect("RenderLayer::rect");
 
-        let self_ = Arc::new(Self { sg: sg_ptr, node_id, dc_key: OsRng.gen(), is_visible, rect });
+        // Monitor for changes to screen_size or scale properties
+        // If so then trigger draw
+        let rect_sub = rect.subscribe_modify();
+
+        let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
+            // Modify task needs a Weak<Self>
+            let modify_task = ex.spawn(async move {
+                loop {
+                    let _ = rect_sub.receive().await;
+                    debug!(target: "app", "Layer rect modified");
+
+                    let Some(self_) = me.upgrade() else {
+                        // Should not happen
+                        panic!("self destroyed before modify_task was stopped!");
+                    };
+
+                    debug!(target: "app", "layer rect property modified");
+                    let sg = sg.lock().await;
+                    // read parent's rect
+                    //self_.draw(&sg).await;
+                }
+            });
+
+            Self {
+                sg: sg_ptr,
+                node_id,
+                dc_key: OsRng.gen(),
+                is_visible,
+                rect,
+                parent_rect: Mutex::new(Rectangle { x: 0., y: 0., w: 0., h: 0. }),
+            }
+        });
 
         Pimpl::RenderLayer(self_)
     }
@@ -373,26 +490,33 @@ impl RenderLayer {
         sg: &SceneGraph,
         parent_rect: &Rectangle<f32>,
     ) -> Option<(u64, Vec<(u64, DrawCall)>)> {
-        debug!("RenderLayer::draw()");
+        debug!(target: "app", "RenderLayer::draw()");
         let node = sg.get_node(self.node_id).unwrap();
 
         if !self.is_visible.get() {
-            debug!("invisible layer node '{}':{}", node.name, node.id);
+            debug!(target: "app", "invisible layer node '{}':{}", node.name, node.id);
             return None
         }
 
-        let Ok(rect) = self.get_rect(parent_rect) else {
+        let Ok(mut rect) = self.get_rect(parent_rect) else {
             panic!("malformed rect property for node '{}':{}", node.name, node.id)
         };
 
+        rect.x += parent_rect.x;
+        rect.y += parent_rect.x;
+
         if !parent_rect.includes(&rect) {
             error!(
+                target: "app",
                 "layer '{}':{} rect {:?} is not inside parent {:?}",
                 node.name, node.id, rect, parent_rect
             );
             return None
         }
 
+        debug!(target: "app", "Parent rect: {:?}", parent_rect);
+        debug!(target: "app", "Viewport rect: {:?}", rect);
+
         // Apply viewport
 
         let mut draw_calls = vec![];
@@ -404,7 +528,7 @@ impl RenderLayer {
                 Pimpl::RenderLayer(layer) => layer.draw(&sg, &rect).await,
                 Pimpl::Mesh(mesh) => mesh.draw(&sg, &rect),
                 _ => {
-                    error!("unhandled pimpl type");
+                    error!(target: "app", "unhandled pimpl type");
                     continue
                 }
             };
@@ -419,6 +543,10 @@ impl RenderLayer {
     }
 }
 
+impl Stoppable for RenderLayer {
+    async fn stop(&self) {}
+}
+
 pub struct Mesh {
     render_api: RenderApiPtr,
     vertex_buffer: miniquad::BufferId,
@@ -491,6 +619,7 @@ impl Mesh {
         sg: &SceneGraph,
         parent_rect: &Rectangle<f32>,
     ) -> Option<(u64, Vec<(u64, DrawCall)>)> {
+        debug!(target: "app", "Mesh::draw()");
         // Only used for debug messages
         let node = sg.get_node(self.node_id).unwrap();
 
@@ -501,11 +630,13 @@ impl Mesh {
             num_elements: self.num_elements,
         };
 
-        let Ok(rect) = self.get_rect(parent_rect) else {
+        let Ok(mut rect) = self.get_rect(parent_rect) else {
             panic!("malformed rect property for node '{}':{}", node.name, node.id)
         };
 
-        // FIXME: all these rects must be aggregated down the tree
+        rect.x += parent_rect.x;
+        rect.y += parent_rect.x;
+
         let scale_x = rect.w / parent_rect.w;
         let scale_y = rect.h / parent_rect.h;
         let model = glam::Mat4::from_translation(glam::Vec3::new(rect.x, rect.y, 0.)) *
@@ -528,7 +659,7 @@ impl Mesh {
 }
 
 impl Stoppable for Mesh {
-    async fn stop(self) {
+    async fn stop(&self) {
         // TODO: Delete own draw call
 
         // Free buffers

+ 31 - 26
bin/darkwallet/src/gfx2.rs

@@ -17,6 +17,7 @@ use std::{
 };
 
 use crate::{
+    app::AsyncRuntime,
     chatview, editbox,
     error::{Error, Result},
     expr::{SExprMachine, SExprVal},
@@ -136,8 +137,9 @@ struct RenderContext<'a> {
 
 impl<'a> RenderContext<'a> {
     fn draw(&mut self) {
-        debug!(target: "gfx", "RenderContext::draw()");
+        //debug!(target: "gfx", "RenderContext::draw()");
         self.draw_call(&self.draw_calls[&0], 0);
+        //debug!(target: "gfx", "RenderContext::draw() [DONE]");
     }
 
     fn draw_call(&mut self, draw_call: &DrawCall, indent: u32) {
@@ -145,8 +147,7 @@ impl<'a> RenderContext<'a> {
         for instr in &draw_call.instrs {
             match instr {
                 DrawInstruction::ApplyViewport(view) => {
-                    debug!(target: "gfx", "{}apply_viewport({:?})", ws, view);
-
+                    //debug!(target: "gfx", "{}apply_viewport({:?})", ws, view);
                     let (_, screen_height) = window::screen_size();
 
                     let view_x = view.x.round() as i32;
@@ -155,11 +156,11 @@ impl<'a> RenderContext<'a> {
                     let view_w = view.w.round() as i32;
                     let view_h = view.h.round() as i32;
 
-                    self.ctx.apply_viewport(view_x, view_y, view_w, view_h);
-                    self.ctx.apply_scissor_rect(view_x, view_y, view_w, view_h);
+                    //self.ctx.apply_viewport(view_x, view_y, view_w, view_h);
+                    //self.ctx.apply_scissor_rect(view_x, view_y, view_w, view_h);
                 }
                 DrawInstruction::ApplyMatrix(model) => {
-                    debug!(target: "gfx", "{}apply_matrix({:?})", ws, model);
+                    //debug!(target: "gfx", "{}apply_matrix({:?})", ws, model);
                     let data: [u8; 64] = unsafe { std::mem::transmute_copy(model) };
                     self.uniforms_data[64..].copy_from_slice(&data);
                     self.ctx.apply_uniforms_from_bytes(
@@ -168,7 +169,7 @@ impl<'a> RenderContext<'a> {
                     );
                 }
                 DrawInstruction::Draw(mesh) => {
-                    debug!(target: "gfx", "{}draw(mesh)", ws);
+                    //debug!(target: "gfx", "{}draw({:?})", ws, mesh);
                     let texture = match mesh.texture {
                         Some(texture) => texture,
                         None => self.white_texture,
@@ -208,6 +209,8 @@ pub enum GraphicsEvent {
 }
 
 struct Stage {
+    async_runtime: AsyncRuntime,
+
     ctx: Box<dyn RenderingBackend>,
     pipeline: Pipeline,
     white_texture: TextureId,
@@ -220,11 +223,21 @@ struct Stage {
 
 impl Stage {
     pub fn new(
+        async_runtime: AsyncRuntime,
         method_rep: mpsc::Receiver<GraphicsMethod>,
         event_pub: PublisherPtr<GraphicsEvent>,
     ) -> Self {
         let mut ctx: Box<dyn RenderingBackend> = window::new_rendering_backend();
 
+        // Maybe should be patched upstream since inconsistent behaviour
+        // Needs testing on other platforms too.
+        #[cfg(target_os = "android")]
+        {
+            let (screen_width, screen_height) = window::screen_size();
+            let event = GraphicsEvent::Resize((screen_width, screen_height));
+            event_pub.notify(event);
+        }
+
         let white_texture = ctx.new_texture_from_rgba8(1, 1, &[255, 255, 255, 255]);
 
         let mut shader_meta: ShaderMeta = shader::meta();
@@ -265,6 +278,7 @@ impl Stage {
         );
 
         Stage {
+            async_runtime,
             ctx,
             pipeline,
             white_texture,
@@ -341,6 +355,7 @@ impl EventHandler for Stage {
 
         loop {
             let Ok(method) = self.method_rep.recv_deadline(deadline) else { break };
+            debug!(target: "gfx", "Received method: {:?}", method);
             match method {
                 GraphicsMethod::NewTexture((width, height, data, sendr)) => {
                     self.method_new_texture(width, height, data, sendr)
@@ -395,27 +410,17 @@ impl EventHandler for Stage {
         let event = GraphicsEvent::Resize((width, height));
         self.event_pub.notify(event);
     }
-}
-
-pub fn run_gui(method_rep: mpsc::Receiver<GraphicsMethod>, event_pub: PublisherPtr<GraphicsEvent>) {
-    #[cfg(target_os = "android")]
-    {
-        android_logger::init_once(
-            android_logger::Config::default().with_max_level(LevelFilter::Debug).with_tag("darkfi"),
-        );
-    }
 
-    #[cfg(target_os = "linux")]
-    {
-        let term_logger = simplelog::TermLogger::new(
-            simplelog::LevelFilter::Debug,
-            simplelog::Config::default(),
-            simplelog::TerminalMode::Mixed,
-            simplelog::ColorChoice::Auto,
-        );
-        simplelog::CombinedLogger::init(vec![term_logger]).expect("logger");
+    fn quit_requested_event(&mut self) {
+        self.async_runtime.stop();
     }
+}
 
+pub fn run_gui(
+    async_runtime: AsyncRuntime,
+    method_rep: mpsc::Receiver<GraphicsMethod>,
+    event_pub: PublisherPtr<GraphicsEvent>,
+) {
     let mut conf = miniquad::conf::Conf {
         high_dpi: true,
         window_resizable: true,
@@ -430,5 +435,5 @@ pub fn run_gui(method_rep: mpsc::Receiver<GraphicsMethod>, event_pub: PublisherP
     conf.platform.apple_gfx_api =
         if metal { conf::AppleGfxApi::Metal } else { conf::AppleGfxApi::OpenGl };
 
-    miniquad::start(conf, || Box::new(Stage::new(method_rep, event_pub)));
+    miniquad::start(conf, || Box::new(Stage::new(async_runtime, method_rep, event_pub)));
 }

+ 37 - 146
bin/darkwallet/src/main.rs

@@ -38,124 +38,32 @@ use crate::{
     scene::{SceneGraph, SceneGraphPtr},
 };
 
-fn start_zmq(scene_graph: SceneGraphPtr) {
-    // detach thread
+#[cfg(target_os = "android")]
+fn panic_hook(panic_info: &std::panic::PanicInfo) {
+    error!("panic occurred: {panic_info}");
+    //error!("panic: {}", std::backtrace::Backtrace::force_capture().to_string());
 }
 
-fn start_sentinel(scene_graph: SceneGraphPtr) {
-    // detach thread
-    // Sentinel should cleanly close when sent a stop signal.
-    let _ = thread::spawn(move || {
-        let mut sentinel = plugin::Sentinel::new(scene_graph);
-        sentinel.run();
-    });
-}
-
-/*
-async fn greensq(render_api: Arc<gfx2::RenderApi>) -> (miniquad::BufferId, miniquad::BufferId) {
-    let x1 = 0.1;
-    let x2 = 0.6;
-    let y1 = 0.1;
-    let y2 = 0.6;
-    let color = [1., 0., 0., 1.];
-
-    let verts = vec![
-        gfx2::Vertex { pos: [x1, y1], color, uv: [0., 0.] },
-        gfx2::Vertex { pos: [x2, y1], color, uv: [1., 0.] },
-        gfx2::Vertex { pos: [x1, y2], color, uv: [0., 1.] },
-        gfx2::Vertex { pos: [x2, y2], color, uv: [1., 1.] },
-    ];
-    let vertex_buffer = render_api.new_vertex_buffer(verts).await.unwrap();
-
-    let indices = vec![0, 2, 1, 1, 2, 3];
-    let index_buffer = render_api.new_index_buffer(indices).await.unwrap();
-
-    let (off_x, off_y) = (0., 0.);
-    let (screen_width, screen_height) = miniquad::window::screen_size();
-    let (scale_x, scale_y) = (1./screen_width, 1./screen_height);
-    let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
-        glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
-    let model = glam::Mat4::IDENTITY;
-
-    // We have to handle window resizing for viewport and matrix
-
-    let dc = gfx2::DrawCall {
-        instrs: vec![
-            //gfx2::DrawInstruction::ApplyViewport(gfx::Rectangle {
-            //    x: 0, y: 0,
-            //    w: screen_width as i32,
-            //    h: screen_height as i32,
-            //}),
-        ],
-        dcs: vec![
-            gfx2::DrawCall {
-                instrs: vec![
-                    gfx2::DrawInstruction::ApplyMatrix(model),
-                    gfx2::DrawInstruction::Draw(gfx2::DrawMesh {
-                        vertex_buffer,
-                        index_buffer,
-                        texture: None,
-                        num_elements: 6
-                    })
-                ],
-                dcs: vec![]
-            }
-        ]
-    };
-    render_api.replace_draw_call(vec![], dc).await;
-    (vertex_buffer, index_buffer)
-}
-
-async fn amain(ex: Arc<smol::Executor<'static>>, render_api: Arc<gfx2::RenderApi>,
-    event_sub: pubsub::Subscription<gfx2::GraphicsEvent>
-    ) {
-
-    let task = ex.spawn(async move {
-        let (vert_buffer, idx_buffer) = greensq(render_api).await;
-        loop {
-            let ev = event_sub.receive().await;
-            debug!("ev: {:?}", ev);
-        }
-    });
-
-    smol::Timer::after(std::time::Duration::from_secs(2)).await;
-
-    let x1 = 0.1;
-    let x2 = 0.95;
-    let y1 = 0.1;
-    let y2 = 0.95;
-    let color = [0., 1., 0., 1.];
-
-    let verts = vec![
-        gfx2::Vertex { pos: [x1, y1], color, uv: [0., 0.] },
-        gfx2::Vertex { pos: [x2, y1], color, uv: [1., 0.] },
-        gfx2::Vertex { pos: [x1, y2], color, uv: [0., 1.] },
-        gfx2::Vertex { pos: [x2, y2], color, uv: [1., 1.] },
-    ];
-    let vertex_buffer2 = render_api.new_vertex_buffer(verts).await.unwrap();
-
-    let dc = gfx2::DrawCall {
-        instrs: vec![
-            gfx2::DrawInstruction::ApplyMatrix(model),
-            gfx2::DrawInstruction::Draw(gfx2::DrawMesh {
-                vertex_buffer: vertex_buffer2,
-                index_buffer,
-                texture: None,
-                num_elements: 6
-            })
-        ],
-        dcs: vec![]
-    };
-    render_api.replace_draw_call(vec![0], dc).await;
-    //render_api.delete_buffer(vertex_buffer);
+fn main() {
+    #[cfg(target_os = "android")]
+    {
+        android_logger::init_once(
+            android_logger::Config::default().with_max_level(LevelFilter::Debug).with_tag("darkfi"),
+        );
 
-    println!("hello!");
-}
-*/
+        std::panic::set_hook(Box::new(panic_hook));
+    }
 
-fn main() {
-    // [x] event pub should be a Publisher
-    // [ ] properties should have post-modify hook used to redraw widgets
+    #[cfg(target_os = "linux")]
+    {
+        let term_logger = simplelog::TermLogger::new(
+            simplelog::LevelFilter::Debug,
+            simplelog::Config::default(),
+            simplelog::TerminalMode::Mixed,
+            simplelog::ColorChoice::Auto,
+        );
+        simplelog::CombinedLogger::init(vec![term_logger]).expect("logger");
+    }
 
     let ex = Arc::new(smol::Executor::new());
     let sg = Arc::new(Mutex::new(SceneGraph::new()));
@@ -168,18 +76,26 @@ fn main() {
     });
 
     let (method_req, method_rep) = mpsc::channel();
+    // The UI actually needs to be running for this to reply back.
+    // Otherwise calls will just hang.
     let render_api = gfx2::RenderApi::new(method_req);
     let event_pub = pubsub::Publisher::new();
 
+    let async_runtime = app::AsyncRuntime::new(ex.clone());
+    async_runtime.start();
+
     let app = app::App::new(sg.clone(), ex.clone(), render_api.clone(), event_pub.clone());
-    let app_task = ex.spawn(app.clone().start());
+    let app_task = ex.spawn(app.start());
+    async_runtime.push_task(app_task);
+    //app.clone().start();
 
     // Nice to see which events exist
     let ev_sub = event_pub.clone().subscribe();
     let ev_relay_task = ex.spawn(async move {
+        debug!(target: "main", "event relayer started");
         loop {
             let Ok(ev) = ev_sub.receive().await else {
-                debug!("Event relayer closed");
+                debug!(target: "main", "Event relayer closed");
                 break
             };
             // Ignore keys which get stuck
@@ -188,39 +104,14 @@ fn main() {
                 gfx2::GraphicsEvent::KeyDown((miniquad::KeyCode::LeftSuper, _, _)) => continue,
                 _ => {}
             }
-            debug!("event: {:?}", ev);
+            debug!(target: "main", "event: {:?}", ev);
         }
     });
-    // End debug code
-
-    let n_threads = std::thread::available_parallelism().unwrap().get();
-    let (signal, shutdown) = smol::channel::unbounded::<()>();
-    let exec_threadpool = thread::spawn(move || {
-        easy_parallel::Parallel::new()
-            // N executor threads
-            .each(0..n_threads, |_| smol::future::block_on(ex.run(shutdown.recv())))
-            .run();
-    });
-
-    gfx2::run_gui(method_rep, event_pub);
-
-    // Close all tasks
-    smol::future::block_on(async {
-        // Perform cleanup code
-        // If not finished in certain amount of time, then just exit
-
-        let mut futures = FuturesUnordered::new();
-        futures.push(zmq_task.cancel());
-        futures.push(ev_relay_task.cancel());
-        futures.push(app_task.cancel());
-        let _: Vec<_> = futures.collect().await;
-
-        app.stop().await;
-    });
+    async_runtime.push_task(ev_relay_task);
 
-    drop(signal);
-    exec_threadpool.join();
-    debug!("Application closed");
+    //let stage = gfx2::Stage::new(method_rep, event_pub);
+    gfx2::run_gui(async_runtime, method_rep, event_pub);
+    debug!(target: "main", "Started GFX backend");
 }
 
 /*

+ 2 - 1
bin/darkwallet/src/prop/mod.rs

@@ -196,7 +196,8 @@ pub struct Property {
     pub subtype: PropertySubType,
     pub defaults: Vec<PropertyValue>,
     pub vals: Mutex<Vec<PropertyValue>>,
-
+    // either a value or an expr must be set
+    //pub exprs: Mutex<Vec<Option<SExprCode>>>,
     pub ui_name: String,
     pub desc: String,