Uncaught Error with Webassembly using Yew Framwork - rust

I'm using Yew to program a theme switcher that by clicking cycles through different themes.
This is my update function. It gets the current theme which is stored in shared state, depending on what would come next inside theme_cycle the theme value inside the shared state gets set to it.
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ChangeTheme => {
let theme_cycle: [&str; 3] = ["light", "dark", "rust"];
let current_theme = self.props.handle.state().theme.clone();
// eval next theme
let next_theme = match theme_cycle.iter().position(|x| x == &current_theme) {
None => theme_cycle[0].to_string(),
Some(i) => {
if i >= (current_theme.len() - 1) {
theme_cycle[0].to_string()
} else {
theme_cycle[i + 1].to_string()
}
},
};
// set next theme
self.props.handle.reduce(move |state| state.theme = next_theme.clone());
// store it inside localstorage
},
Msg::ToggleLangDropdown => self.show_dropdown = !self.show_dropdown,
};
true
}
But if the theme val inside shared state is "rust" and I click the button again that calls Msg::ChangeTheme, the theme should be set to "light" but instead my code panics and I get an "Uncaught Error: undefined" inside the Browser Console.

I've found a workaround; instead of using an array and accessing the values, I've tried to do the same task but just with iterators and made sure that the update function takes no ownership of any variable outside the function itself (I don't really know if that is really necessary though...)
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ChangeTheme => {
let theme_cycle = ["light".to_string(), "dark".to_string(), "rust".to_string()];
let current_theme = self.props.handle.state().theme.clone();
let indexof_current_theme = match theme_cycle.iter().position(|x| x.to_string() == current_theme) {
None => 0,
Some(x) => x.clone(),
};
let next_theme = match theme_cycle.iter().nth(indexof_current_theme + 1) {
None => theme_cycle.iter().nth(0).unwrap().clone(),
Some(x) => x.clone(),
};
self.props.handle.reduce(move |state| state.theme = next_theme.to_string());
},
Msg::ToggleLangDropdown => self.show_lang_dropdown = !self.show_lang_dropdown,
Msg::ToggleThemeDropdown => self.show_theme_dropdown = !self.show_theme_dropdown,
};
true
}
Still would be cool if anyone knows what I did wrong in my first attempt.

Related

Why does a value created inside a function borrow and how can I avoid this pattern?

I'm new to rust but an engineer of over 6 years in various other languages from Javascript to Go.
I'm wondering why here the value is borrowed when I convert the response body to an "object".
I understand that the function owns the value and then the value is destroyed when the function returns BUT functions exist to create and return values. So there's clearly something fairly big I'm missing here. Can someone set me straight?
let response = match self
.client
.index(IndexParts::IndexId(index, id))
.body(json!({
"index": index,
"body": doc,
}))
.send()
.await
{
Ok(response) => response,
Err(err) => {
return Err(Box::new(err));
}
};
let response_body = match response.json::<Value>().await {
Ok(response_body) => response_body,
Err(err) => {
return Err(Box::new(err));
}
};
let response_map = response_body.as_object();
Ok(response_map)
I understand that the function owns the value and then the value is destroyed when the function returns BUT functions exist to create and return values. So there's clearly something fairly big I'm missing here.
You need to return an owned value, not a reference into a local. I assume what you're doing now boils down to:
fn foo() -> &Map<String, Value> {
let x = serde_json::json!({}); // except you get it by http
x.as_object().unwrap() // except you do proper error handling
}
This doesn't compile because you're returning the reference to a local value. Instead, you need to return the value itself:
fn foo() -> Map<String, Value> {
let x = serde_json::json!({}); // except you get it by http
match x {
Value::Object(o) => o,
_ => unreachable!(), // you'd return Err(...)
}
}
But even this is more complicated than you need. Since you already deserialize the value yourself, and handle the errors, you can simply ask serde to deliver a Map<String, Value> to begin with:
let response_body = match response.json::<Map<String, Value>>().await {
Ok(response_body) => response_body,
Err(err) => ...
};
Of course, you'll also need to adjust the return type to return the actual value instead of a reference.

Yew: Difficulty with nested callbacks

I'm attempting to do something that I feel is pretty basic: I have a pulldown, and I'd like the onchange event for that pulldown to cause the program to fetch some data from the backend based on the user's input. (And then, you know, give the user more options based on the first thing they picked. Really simple, and seems like I ought to have been able to find an easy way to do this.)
Full code for this minimal (failing) example is at: https://github.com/djmcmath/broken-yew
But the relevant bit, which doesn't behave correctly, is below:
The view function renders, delightfully, an iterated list. I pass in a callback, so it knows what to do on the "onchange" event.
The callback gets executed, which makes me very happy. But it isn't calling the Msg::GetData. This compiles, which is nice, but it doesn't work, which is less nice.
I've spent, I'm ashamed to admit, several weeks of my spare time fighting with this. I think it has something to do with scopes and lifetimes. I think that the way I'm making this compile -- by cloning the context and using "move" disconnects it from the actual context that I need to make this work. But every variation on the theme that I've been able to find in examples and references complains about scope or lifetimes.
Thanks in advance for the help.
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::GetData(value) => {
log::info!("Start 'fetch' with user-selected value: {}", value);
ctx.link().send_future(async {
match fetch_markdown("url_shortened_for_clarity").await {
Ok(md) => Msg::SetMarkdownFetchState(FetchState::Success(md)),
Err(err) => Msg::SetMarkdownFetchState(FetchState::Failed(err)),
}
});
false
},
Msg::SetMarkdownFetchState(fetch_state) => {
let mut wr = WebReturn { term_id: 0, dow: 0, dep_time_num: 0 };
match fetch_state {
FetchState::Success(s) => { wr = serde_json::from_str(&s).expect(&format!("Poorly formatted JSON! {}", s).to_string()); },
FetchState::Failed(f) => { log::info!("Fetch failed: {}", f); },
FetchState::NotFetching => {},
FetchState::Fetching => {}
};
log::info!("term_id (3) : {}, dep_time_num (12000) : {}, and dow (3) : {}", wr.term_id, wr.dep_time_num, wr.dow);
true
}
}
}
fn view(&self, ctx:&Context<Self>) -> Html {
let ctx_link = ctx.link().clone();
let my_callback: Callback<String> = Callback::from(move |value: String| {
let val_as_num = value.parse::<i32>().unwrap_or(0);
log::info!("Returned value: {}", val_as_num);
ctx_link.callback(|val_as_num: i32| Msg::GetData(val_as_num));
});
html! {
<div>
{ self.render_list(&self.props.term_list, my_callback) }
</div>
}
}
This line does not "call back" to your component, it creates a callback and then doesn't call it:
ctx_link.callback(|val_as_num: i32| Msg::GetData(val_as_num));
You need to instead call .send_message() in your callback or, better yet, create your original callback with .callback():
let my_callback = ctx_link.callback(|value: String| {
let val_as_num = value.parse::<i32>().unwrap_or(0);
log::info!("Returned value: {}", val_as_num);
Msg::GetData(val_as_num)
});

Accept multiple values on proc macro attribute

I wanted to be able to retrieve the content from an attribute like this:
#[foreign_key(table = "some_table", column = "some_column")]
This is how I am trying:
impl TryFrom<&&Attribute> for EntityFieldAnnotation {
type Error = syn::Error;
fn try_from(attribute: &&Attribute) -> Result<Self, Self::Error> {
if attribute.path.is_ident("foreign_key") {
match attribute.parse_args()? {
syn::Meta::NameValue(nv) =>
println!("NAME VALUE: {:?}, {:?}, {:?}",
nv.path.get_ident(),
nv.eq_token.to_token_stream(),
nv.lit.to_token_stream(),
),
_ => println!("Not interesting")
}
} else {
println!("No foreign key")
}
// ... More Rust code
}
Everything works fine if I just put in there only one NameValue. When I add the comma,
everything brokes.
The only error:
error: unexpected token
How can I fix my logic to enable the possibility of have more than just one NameValue?
Thanks
UPDATE: While writing this answer, I had forgotten that Meta has List variant as well which gives you NestedMeta. I would generally prefer doing that instead of what I did in the answer below for more flexibility.
Although, for your particular case, using Punctuated still seems simpler and cleaner to me.
MetaNameValue represents only a single name-value pair. In your case it is delimited by ,, so, you need to parse all of those delimited values as MetaNameValue instead.
Instead of calling parse_args, you can use parse_args_with along with Punctuated::parse_terminated:
use syn::{punctuated::Punctuated, MetaNameValue, Token};
let name_values: Punctuated<MetaNameValue, Token![,]> = attribute.parse_args_with(Punctuated::parse_terminated).unwrap(); // handle error instead of unwrap
Above name_values has type Punctuated which is an iterator. You can iterate over it to get various MetaNameValue in your attribute.
Updates based on comments:
Getting value out as String from MetaNameValue:
let name_values: Result<Punctuated<MetaNameValue, Token![,]>, _> = attr.parse_args_with(Punctuated::parse_terminated);
match name_values {
Ok(name_value) => {
for nv in name_value {
println!("Meta NV: {:?}", nv.path.get_ident());
let value = match nv.lit {
syn::Lit::Str(v) => v.value(),
_ => panic!("expeced a string value"), // handle this err and don't panic
};
println!( "Meta VALUE: {:?}", value )
}
},
Err(_) => todo!(),
};

How to switch screens in a rust tui app (termion + tui-rs)

I have a tui app where a user is presented with some choices through a list. Once they navigate to the choice they want and hit enter I'd like to take them to the "next" screen.
It's more complicated than just clearning existing text and printing new one because I also need to replace keybindings and basically start a new tui-rs loop. More below.
Code for Screen 1:
pub fn draw_screen() -> Result<(), Box<dyn Error>> {
// Terminal initialization
let stdout = io::stdout().into_raw_mode()?;
let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let events = Events::new();
loop {
terminal.draw(|f| {
// user shown a list they can navigate through using arrow keys
});
match events.next()? {
Event::Input(input) => match input {
Key::Char('q') => {
break;
}
Key::Char('\n') => {
// this is where I need to "send" them to a new screen
}
Key::Down => {
// my_list won't exist on next screen
my_list.items.next();
}
Key::Up => {
my_list.items.previous();
}
_ => {}
},
_ => {}
}
}
Ok(())
}
As can be seen the keybindings at the bottom are specific to this screen. Eg on the next screen there's not going to be a my_list and instead there might be a my_another_list or my_box or nothing at all.
So if all I did was clear the text, I'd still be left inside the same loop with the same keybindings - doesn't work.
What's the right way to initiate a new loop with fresh keybindings?

Gstreamer Editing Service rust bindings error while setting render settings

I'm very new to gstreamer and rust and am trying to render a video made from sections of other videos. Based on the docs, gstreamer-rs examples, and this question about doing the same thing in python, I think my code looks pretty good, but throws errors.
This is my code:
use gstreamer as gst;
use gstreamer::{ElementExt, ElementExtManual, GstObjectExt};
use gstreamer_editing_services as ges;
use gstreamer_editing_services::{GESPipelineExt, LayerExt, TimelineExt};
use gstreamer_pbutils as gst_pbutils;
use gstreamer_pbutils::{EncodingProfileBuilder};
pub fn clip_video() {
match gst::init() {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
match ges::init() {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
let timeline = ges::Timeline::new_audio_video();
let layer = timeline.append_layer();
let pipeline = ges::Pipeline::new();
match pipeline.set_timeline(&timeline) {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
let video_profile = gst_pbutils::EncodingVideoProfileBuilder::new()
.name("h.264")
.description("h.264-profile")
.format(&gst::caps::Caps::new_simple("video/x-h264", &[]))
.build()
.unwrap();
let audio_profile = gst_pbutils::EncodingAudioProfileBuilder::new()
.name("mp3")
.description("mp3-profile")
.format(&gst::caps::Caps::new_simple(
"audio/mpeg",
&[("mpegversion", &"1"), ("layer", &"3")],
))
.build()
.unwrap();
let contianer_profile = gst_pbutils::EncodingContainerProfileBuilder::new()
.name("default-mp4-profile")
.description("mp4-with-h.264-mp3")
.format(&gst::caps::Caps::new_simple(
"video/quicktime",
&[("variant", &"iso")],
))
.enabled(true)
.add_profile(&video_profile)
.add_profile(&audio_profile)
.build()
.unwrap();
let asset = ges::UriClipAsset::request_sync("file:///home/ryan/repos/auto-highlighter-processing-service/input/test-video.mp4").expect("Failed to create asset");
match layer.add_asset(
&asset,
0 * gst::SECOND,
10 * gst::SECOND,
10 * gst::SECOND,
ges::TrackType::CUSTOM,
) {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
match pipeline.set_render_settings("file:///home/ryan/repos/auto-highlighter-processing-service/output/test-video.mp4", &contianer_profile){
Err(e) => eprintln!("{:?}", e),
_ => (),
}
match pipeline.set_mode(ges::PipelineFlags::RENDER) {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
match pipeline.set_state(gst::State::Playing) {
Err(e) => eprintln!("{:?}", e),
_ => (),
}
let bus = pipeline.get_bus().unwrap();
for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => break,
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.get_src().map(|s| s.get_path_string()),
err.get_error(),
err.get_debug()
);
break;
}
_ => (),
}
}
}
The errors that I am getting:
BoolError { message: "Failed to set render settings", filename: "/home/ryan/.cargo/registry/src/github.com-1ecc6299db9ec823/gstreamer-editing-services-0.16.5/src/auto/pipeline.rs", function: "gstreamer_editing_services::auto::pipeline", line: 228 }
StateChangeError
I'm struggling to find what to do about these errors or what the problem could be. From what I know I'm using the set_render_settings() and set_mode() functions correctly.
I didn't try running your code, but one problem I found when reading was the following
.format(&gst::caps::Caps::new_simple(
"audio/mpeg",
&[("mpegversion", &"1"), ("layer", &"3")],
))
The "mpegversion" and "layer" fields of the caps are not strings but integers. If you use them as such it should work (or at least work better)
.format(&gst::caps::Caps::new_simple(
"audio/mpeg",
&[("mpegversion", &1i32), ("layer", &3i32)],
))
Everything else looks correct to me.
You can find more details about such errors by making use of the GStreamer debugging system. You can enable that via the GST_DEBUG environment variable, e.g. by setting that to 6.
Although this answer is over a year late, I thought I'd post anyway, as examples in Rust for GES are sparse, with only a single (though good) example of applying a 'agingtv' effect on the gstreamer-rs repo. Additionally, the OP's sample code above will result in rendering 10 seconds of black video, and does not, as the OP mentioned, result in the desired output.
Using the example listed in the original question above:
On gstreamer-rs 0.19:
Build video profile (note the caps now must be passed via builder() ):
let video_profile = gstreamer_pbutils::EncodingVideoProfile::builder(
&gst::Caps::builder("video/x-h264").build(),
)
.name("video_profile")
.build();
Build the Audio profile:
let audio_profile = gstreamer_pbutils::EncodingAudioProfile::builder(
&gstreamer::Caps::new_simple("audio/x-aac", &[]),
)
.name("audio_profile")
.build();
Build the Container profile:
let container_profile = gstreamer_pbutils::EncodingContainerProfile::builder(
&gstreamer::Caps::new_simple("video/x-matroska", &[]),
)
.name("container_profile")
.add_profile(&audio_profile)
.add_profile(&video_profile)
.build();
Note: as an alternative you can build the whole encoding profile in one go from the DiscovererInfo if you ran the gst-discover on the media first. This will result in an output file very similar to the input file in it's encoding settings.
let encoding_profile =
gstreamer_pbutils::EncodingProfile::from_discoverer(&m_info.discover_info)?;
The following example will clip the video, add a transition, and merge in an additional clip to fade to:
let timeline = ges::Timeline::new_audio_video();
timeline.set_auto_transition(true);
let layer = timeline.append_layer();
let pipeline = ges::Pipeline::new();
pipeline.set_timeline(&timeline)?;
let audio_profile = gstreamer_pbutils::EncodingAudioProfile::builder(
&gstreamer::Caps::new_simple("audio/x-aac", &[]),
)
.name("audio_profile")
.build();
let video_profile = gstreamer_pbutils::EncodingVideoProfile::builder(
&gst::Caps::builder("video/x-h264").build(),
)
.name("video_profile")
.build();
let container_profile = gstreamer_pbutils::EncodingContainerProfile::builder(
&gstreamer::Caps::new_simple("video/x-matroska", &[]),
)
.name("container_profile")
.add_profile(&audio_profile)
.add_profile(&video_profile)
.build();
/* alternatively
let encoding_profile = gstreamer_pbutils::EncodingProfile::from_discoverer(&m_info.discover_info)?;
*/
/* original video */
let clip = ges::UriClip::new("file:///home/ryan/repos/auto-highlighter-processing-service/input/test-video.mp4")?;
layer.add_clip(&clip)?;
clip.set_inpoint(gst::ClockTime::from_seconds(0));
clip.set_duration(gst::ClockTime::from_seconds(10));
/* video to transition to with a fade */
let clip_transition_to = ges::UriClip::new("/some/2/second/video/file.mp4")?;
clip_transition_to.set_start(gst::ClockTime::from_seconds(9)); //this should overlap the original video clip, but not completely
clip_transition_to.set_inpoint(gst::ClockTime::from_seconds(0));
clip_transition_to.set_duration(gst::ClockTime::from_seconds(2));
layer.add_clip(&clip_transition_to)?;
pipeline.set_render_settings("file:///home/ryan/repos/auto-highlighter-processing-service/output/test-video.mp4", &container_profile)?; //or &encoding_profile
pipeline.set_mode(ges::PipelineFlags::RENDER)?;
pipeline.set_state(gst::State::Playing)?;
let bus = pipeline
.bus()
.expect("Pipeline without bus. Shouldn't happen!");
for msg in bus.iter_timed(gst::ClockTime::NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => break,
MessageView::Error(err) => {
pipeline.set_state(gst::State::Null)?;
match err.details() {
Some(details) if details.name() == "error-details" => details
.get::<&ErrorValue>("error")
.unwrap()
.clone()
.0
.lock()
.unwrap()
.take()
.map(Result::Err)
.expect("error-details message without actual error"),
_ => Err({
let err_src = msg
.src()
.map(|s| String::from(s.path_string()))
.unwrap_or_else(|| String::from("None"));
log!(
Level::Error,
"A GStreamer Error was Encountered {}",
&err_src
);
ErrorMessage {
src: err_src,
error: err.error().to_string(),
debug: err.debug(),
source: err.error(),
}
.into()
}),
}?;
}
MessageView::StateChanged(state_changed) => {
// if state_changed.src().map(|s| s == decodebin).unwrap_or(false)
// && state_changed.current() == gst::State::Playing
// {
// // Generate a dot graph of the pipeline to GST_DEBUG_DUMP_DOT_DIR if defined
// let bin_ref = decodebin.downcast_ref::<gst::Bin>().unwrap();
// bin_ref.debug_to_dot_file(gst::DebugGraphDetails::all(), "PLAYING");
// }
let msg = format!(
"State changed from {:?}: {:?} -> {:?} ({:?})",
state_changed.src().map(|s| s.path_string()),
state_changed.old(),
state_changed.current(),
state_changed.pending()
);
log!(Level::Debug, "{}", msg)
}
_ => (),
}
}
let log_msg = "Play run complete, changing state...";
log!(Level::Info, "{}", &log_msg);
pipeline.set_state(gst::State::Null)?;
The result will be a 10 second video, with a fade out to a 2 second video (e.g. something that says "the end", etc).
I hope this helps someone. It took a some reading and research to achieve the desired effect, and hopefully this will help someone else on the same journey.
gstreamer is excellent software and, although it's been a bit of work to get things functional, it's been great to work with the rust bindings.

Resources