rksm / hot-lib-reloader-rs

Reload Rust code without app restarts. For faster feedback cycles.
MIT License
597 stars 19 forks source link

`Iced` sample code keeps crashing at every `lib` reload #25

Open lucatrv opened 1 year ago

lucatrv commented 1 year ago

I've been experimenting with the following Iced sample code, but it keeps crashing at every lib reload. I can't figure out if I'm mistaking something or if it's due to a hot-lib-reloader-rs' issue, can you please help me out?

I tested the attached code both on Windows 10 and Arch Linux, with rustc version 1.65.0 and 1.67.0-nightly, and I attempted also using cargo run --features iced/glow with no luck.

commands:
  bin: |
    cargo run
  lib: |
      cargo watch -w lib -x 'build -p lib'

bin

[workspace]
members = ["lib"]

[package]
name = "bin"
version = "0.1.0"
edition = "2021"

[dependencies]
hot-lib-reloader = "0.6.4"
iced = "0.5.2"
lib = { path = "lib" }
#[hot_lib_reloader::hot_module(dylib = "lib")]
mod hot_lib {
    use iced::{Element, Theme};
    pub use lib::*;
    hot_functions_from_file!("lib/src/lib.rs");
}

use hot_lib::*;
use iced::{Element, Sandbox, Settings, Theme};

fn main() -> iced::Result {
    App::run(Settings::default())
}

#[derive(Debug, Default)]
struct App {
    state: State,
}

impl Sandbox for App {
    type Message = Message;

    fn new() -> Self {
        Self::default()
    }

    fn title(&self) -> String {
        title()
    }

    fn update(&mut self, message: Message) {
        update(&mut self.state, message)
    }

    fn view(&self) -> Element<Message> {
        view(&self.state)
    }

    fn theme(&self) -> Theme {
        theme(&self.state)
    }
}

lib

[package]
name = "lib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["rlib", "dylib"]

[dependencies]
iced = "0.5.2"
use iced::widget::{
    button, column, horizontal_rule, horizontal_space, radio, row, text, vertical_space,
};
use iced::{Alignment, Element, Length, Theme};

#[derive(Debug)]
pub struct State {
    text_size: u16,
    theme: Theme,
}

impl Default for State {
    fn default() -> Self {
        Self {
            text_size: 100,
            theme: Theme::default(),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ThemeType {
    Light,
    Dark,
}

#[derive(Debug, Clone)]
pub enum Message {
    ThemeChanged(ThemeType),
    IncrementTextSizePressed,
    DecrementTextSizePressed,
}

#[no_mangle]
pub fn title() -> String {
    String::from("Hello World - HotIce")
}

#[no_mangle]
pub fn update(state: &mut State, message: Message) {
    let size_change = 10;
    match message {
        Message::ThemeChanged(theme) => {
            state.theme = match theme {
                ThemeType::Light => Theme::Light,
                ThemeType::Dark => Theme::Dark,
            }
        }
        Message::IncrementTextSizePressed => {
            if state.text_size <= 200 - size_change {
                state.text_size += size_change;
            }
        }
        Message::DecrementTextSizePressed => {
            if state.text_size >= size_change {
                state.text_size -= size_change;
            }
        }
    }
}

#[no_mangle]
pub fn view(state: &State) -> Element<Message> {
    let choose_theme = [ThemeType::Light, ThemeType::Dark].into_iter().fold(
        column![text("Choose a theme:")].spacing(10),
        |column, theme| {
            column.push(radio(
                format!("{:?}", theme),
                theme,
                Some(match state.theme {
                    Theme::Light => ThemeType::Light,
                    Theme::Dark => ThemeType::Dark,
                    Theme::Custom { .. } => panic!(),
                }),
                Message::ThemeChanged,
            ))
        },
    );

    column![
        row![choose_theme, horizontal_space(Length::Fill)],
        horizontal_rule(20),
        vertical_space(Length::Fill),
        text("zHello, world!").size(state.text_size),
        vertical_space(Length::Fill),
        row![
            button("Increment Text Size").on_press(Message::IncrementTextSizePressed),
            button("Decrement Text Size").on_press(Message::DecrementTextSizePressed),
        ]
        .spacing(20)
    ]
    .spacing(20)
    .padding(20)
    .align_items(Alignment::Center)
    .into()
}

#[no_mangle]
pub fn theme(state: &State) -> Theme {
    state.theme.clone()
}
rksm commented 1 year ago

Hi, thanks or the report. I tested it on macos initially and can confirm that it still works there. I'll give it a try with Linux.

rksm commented 1 year ago

Yeah on Ubuntu I get a crash, too. Seems that the issue is in iced_native::user_interface::UserInterface::update, the reload might mess with the data that gets ManuallyDroped there. I'll take a look over the holidays if this can be solved with either a custom command or a manual run loop.

rksm commented 1 year ago

Sorry, didn't get enough time to fix this. Happy to accept contributions.

Not quite sure why this works in macos but not under Windows/Linux.

The issue manifests as a segmentation fault when dealing with the root and overlay Elements at UserInterface::update when wrapping those with a ManuallyDrop. I think the ManuallyDrop isn't the root cause, something about the rendering state might become invalid through the reload.

For now I'll add a note to the example and maybe someone with deeper knowledge of iced than me can look at this?!

Imberflur commented 1 year ago

Hi, I investigated this a bit.

Here is what happens:

Potential solutions:

(note: this is all on the iced version used above (0.5.2) so some details could have changed)

(edit: I would guess that there might already be something preventing the lib from being unloaded on macos, but I can't test that myself)

rksm commented 1 year ago

Thanks! Gonna take a look at this soon.