[Rust] iced: 백그라운드 처리
버전 0.13 기준이다.
간단한 앱이라면 사용자 액션에 의한 이벤트만으로도 충분한 처리를 할 수 있겠으나.
로직이 조금만 복잡해져도 백그라운드 처리에 대한 요구사항은 생길 수 있다.
내 경우에는 sound 플레이어였는데, 당연히 음악이 끝나면 다음 음악을 자동으로 재생하고, 바뀐 음악에 대한 렌더링까지 반영해줘야 했다.
백그라운드 스레드
백그라운드 처리를 하려면 당연히 백그라운드 스레드를 추가로 띄워야할 것이다.
근데 여기서 문제는, 백그라운드 스레드에서 iced 앱에 이벤트를 날릴 방법이 마땅치 않다는 것이다.
알다시피 iced는 elm 아키텍쳐를 따르므로 모든 UI 변경은 이벤트를 기반으로 동작한다.
그러면 백그라운드 변경에 의한 변경 이벤트는 어떤 방식으로 처리해야할까?
Foreground Tick 처리
다행히도 iced는 이런 상황을 위해서 subscription이라는 기능을 제공한다.
먼저 tokio 피쳐를 추가해준다. Foreground 처리를 위한 백그라운드 프로세스를 띄워야 하기 때문이다.
그리고 이런 느낌으로 반복을 어떻게 처리할지를 결정한다.
이 경우에는 2초마다, ForegroundTick이라는 이벤트를 전송한다는 의미가 된다.
저 이벤트는 해당 시점에 대한 값인 Instant를 받아야 한다.
그리고 저 함수를 등록해주면 끝이다.
이벤트가 날라오면 그냥 카운트를 증가시키도록 했다.
이대로 실행해보면

뒤에서 뺑뺑 돌면서 2초마다 이벤트를 날려주고, 카운트도 점진적으로 증가할 것이다.
그리고 앱 포커스를 나가면 귀신같이 끊기고, 포커스를 잡으면 다시 시작된다.
여기서는 초 단위로 했지만, 밀리세컨드 단위로는 돌아야 유의미한 반응성이 나올 것이다.
밀리세컨드 단위로 돌면서, 백그라운드 스레드에서 처리한 결과물을 꺼내와서 UI에 반영하고, 그런 식으로 처리하면 된다.
예제 코드
use std::time::{Duration, Instant};
use iced::widget::{container, text};
use iced::{Color, Element, Length, Settings, Size, Subscription};
pub struct MainApp {
count: i32,
}
#[derive(Debug, Clone)]
pub enum Message {
ForegroundTick(Instant),
}
impl MainApp {
pub fn new() -> Self {
let app = Self { count: 0 };
app
}
pub fn theme(&self) -> iced::Theme {
iced::Theme::Dracula
}
pub fn update(&mut self, message: Message) {
match message {
Message::ForegroundTick(time) => {
println!("ForegroundTick: {:?}", time);
self.count += 1;
}
}
}
pub fn view(&self) -> Element<Message> {
let content = container(
text(format!("Count: {}", self.count))
.size(50)
.color(Color::WHITE),
)
.width(Length::Fill)
.height(Length::Fill)
.into();
content
}
pub fn subscription(&self) -> iced::Subscription<Message> {
let tick = iced::time::every(Duration::from_millis(2000)).map(Message::ForegroundTick);
Subscription::batch(vec![tick])
}
}
impl Default for MainApp {
fn default() -> Self {
Self::new()
}
}
fn main() -> iced::Result {
let setting = Settings::default();
iced::application("test", MainApp::update, MainApp::view)
.settings(setting)
.resizable(false)
.window_size(Size::new(600.0, 600.0))
.theme(MainApp::theme)
.subscription(MainApp::subscription)
.run()
}