Skip to content

Instantly share code, notes, and snippets.

@wallabra
Created January 30, 2025 18:24
Show Gist options
  • Save wallabra/196fd00da76e0abe62e48e64828b17ed to your computer and use it in GitHub Desktop.
Save wallabra/196fd00da76e0abe62e48e64828b17ed to your computer and use it in GitHub Desktop.
Integrating tokio into vizia

Tokio-Vizia Integration Demo

This shows how Tokio concurrency can be used inside Vizia.

Usually, Vizia expects you to create a new system thread every time you want to spawn a concurrent task (see vizia::context::Context::spawn). System threads are usually heavier than async concurrency, especially with a high performance library like Tokio, so it would be desirable to be able to use both.

Fortunately, by making main into an async routine and running vizia's main event loop inside of it, it is possible to spawn Tokio tasks outside of it. Whenever a Tokio task isn't running (e.g. during a sleep or when waiting for a resource), execution is passed back to vizia.

I'm not sure whether work-stealing (rt-multi-thread instead of just rt) is necessary for this to work seamlessly; I believe not, but I only tested with work-stealing, so experiment at your own peril.

In this demo, the async aspect is demonstrated by having every counter increment request be delayed by 1 second, by spawning a new task that awaits tokio::sleep before emitting the actual increment event AppEvent::DelayElapse back into vizia.

While I don't plan on using vizia on the long term, due to it being retained-mode and more well-suited for developers familiar with reactive frameworks like React, I thought this was an interesting experiment to share with y'all Rustaceans. :)

extern crate tokio; // features = ['rt-multi-thread', 'macros']
extern crate vizia;
use std::collections::VecDeque;
use tokio::time::sleep;
use vizia::modifiers::LayoutModifiers;
use vizia::prelude::*;
pub enum AppEvent {
Count,
DelayElapse,
}
#[derive(Clone)]
pub struct DelayList(VecDeque<()>);
impl Data for DelayList {
fn same(&self, other: &Self) -> bool {
other.0.len() == self.0.len() && self.0.iter().eq(other.0.iter())
}
}
impl Default for DelayList {
fn default() -> Self {
Self(Default::default())
}
}
#[derive(Default, Lens)]
pub struct AppData {
pub total_count: u32,
pub count_delays: DelayList,
}
impl Model for AppData {
fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
use AppEvent::{Count, DelayElapse};
event.map(|app_event, _meta| match app_event {
Count => {
self.count_delays.0.push_back(());
tokio::spawn(delay_count(cx.get_proxy()));
}
DelayElapse => {
self.count_delays.0.pop_front();
self.total_count += 1;
}
});
}
//
// fn name(&self) -> Option<&'static str> {
// format!("Delayed Counter ({} delays)", self.count_delays.len())
// }
}
async fn delay_count(mut cx: ContextProxy) {
sleep(Duration::from_secs(1)).await;
cx.emit(AppEvent::DelayElapse).unwrap();
}
#[tokio::main]
async fn main() -> Result<(), ApplicationError> {
Application::new(|cx| {
AppData::default().build(cx);
HStack::new(cx, |cx| {
VStack::new(cx, |cx| {
Label::new(cx, "Delay Counter");
HStack::new(cx, |cx| {
Button::new(cx, |cx| Label::new(cx, "Count"))
.on_press(|ex| ex.emit(AppEvent::Count))
.class("increment");
Label::new(cx, AppData::total_count).class("count");
})
.child_space(Pixels(5.0))
.col_between(Pixels(10.0));
HStack::new(cx, |cx| {
Binding::new(cx, AppData::count_delays, |cx, delay_list| {
for _ in 0..delay_list.get(cx).0.len() {
Element::new(cx)
.width(Pixels(50.0))
.height(Pixels(50.0))
.background_color(Color::rgb(255, 255, 64));
}
})
})
.height(Pixels(90.0))
.background_color(Color::rgba(0, 0, 0, 40))
.left(Pixels(40.0))
.right(Pixels(40.0))
.child_space(Pixels(5.0))
.col_between(Pixels(10.0))
.child_left(Pixels(12.0))
.child_right(Pixels(12.0))
.child_top(Pixels(5.0))
.child_bottom(Pixels(5.0));
})
.child_space(Stretch(1.0))
.row_between(Pixels(40.0));
})
.child_space(Stretch(1.0))
.col_between(Pixels(20.0))
.background_color(Color::rgba(0, 0, 32, 5));
})
.title("Async Counter")
.inner_size((800, 300))
.run()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment