I am learning Rust. I am creating a desktop app which read thousand/million rows of data in csv file and then transfer them one by one using tauri event.
Result: Rust has no problem read through the file (under 5 seconds). On the frontend side, my React app seems unable to keep up with events. On the screen, the altitude value is updated intermittently.
How to handle this situation is React? or What did I do wrong?
React side:
// App.js
import { listen } from '#tauri-apps/api/event';
import { useEffect, useCallback, useState } from 'react';
import { invoke } from '#tauri-apps/api/tauri'
const App = () => {
const [altitude, setAltitude] = useState("0");
useEffect(() => {
listen('rust-event', myCallback)
}, [])
const myCallback = useCallback((e) => {
console.log(e);
setAltitude(e.payload);
},[])
const handleClick = async () => {
invoke('my_custom_command').catch(error => console.log(error));
};
return (
<div>
<button onClick={handleClick}>Click Me To Start Fetching!</button>
<span>{altitude}</span>
</div>
);
}
export default App;
Tauri side:
// main.rs
use arrow::csv;
use arrow::datatypes::{DataType, Field, Schema};
use std::fs::File;
use std::sync::Arc;
use arrow::array::{StringArray, ArrayRef};
#[tauri::command]
async fn my_custom_command(window: tauri::Window) {
let schema = Schema::new(vec![
Field::new("altitude", DataType::Utf8, false)
]);
// Open file
let file = File::open("src/data.csv").unwrap();
// Get csv Reader using schema
let mut csv = csv::Reader::new(file, Arc::new(schema), true, None, 1, None, None);
// Loop through each row
while let Some(m) = csv.next() {
let n = m.unwrap();
// Get reference of array of a column
let col: &ArrayRef = n.column(0);
// Cast the reference of array to array of string
let col = col.as_any().downcast_ref::<StringArray>().unwrap();
// Get value from the array using index
let v = col.value(0);
println!("{}", col.value(0));
// Send each value through an event
window
.emit("rust-event", v)
.expect("failed to emit");
}
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("failed to run app");
}
I personally do not suggest what you are doing as you can get a stack overflow.
The best would be to emit them in batch, you can fill a local buffer, when there are X elements (or the end is reached), emit the event, and clear the buffer.
Example
#[tauri::command]
async fn my_custom_command(window: tauri::Window) {
// Your code
// [...]
// send 20 elements in the Vec (array)
let should_trigger_at = 20;
// local buffer
let local_buffer: Vec<String> = Vec::new();
// Loop through each row
while let Some(m) = csv.next() {
let n = m.unwrap();
// Get reference of array of a column
let col: &ArrayRef = n.column(0);
// Cast the reference of array to array of string
let col = col.as_any().downcast_ref::<StringArray>().unwrap();
// Get value from the array using index
let v = col.value(0);
println!("{}", col.value(0));
// add the value in the buffer
local_buffer.push(col.value(0));
if local_buffer.len() == should_trigger_at {
// Send each value through an event
window
.emit("rust-event", local_buffer)
.expect("failed to emit");
// reset local buffer
local_buffer = Vec::new();
}
}
// if buffer not empty, lets emit the values
if local_buffer.len() > 0 {
window
.emit("rust-event", local_buffer)
.expect("failed to emit");
}
// [...]
// Your code
}
Please note; doing this will send an Array of String to the Webview instead of a String.
Well, I guess Rust is too fast :) React is unable to handle the speed.
I slow down the event emit with settimeout rust lib and I am happy with it for now.
// Before emit an event, delay it 100 microsecond;
set_timeout(Duration::from_micros(100)).await;
window
.emit("rust-event", v)
.expect("failed to emit");
Related
Hi I am building function to recursively "denest" a object of following interface:
export interface IUnit {
code: string
artifacts: IArtifact[]
units: IUnit[]
}
The idea is that I have a separate array that I returns with every immersion and after each return the returned array is "pushed" to local array and so on...
the function is as following:
const denestList = async (incomingUnit: IUnit): Promise<{ allUnits: IUnit[], allArtifacts: IArtifact[] }> => {
var units = [incomingUnit];
var artifacts = [...incomingUnit.artifacts];
//Array is full
console.log(unit.units)
//Array is empty
console.log(unit.units.length)
console.log([...unit.units])
console.log(Array.from(unit.units)?.length)
for (unit of incomingUnit.units) {
console.log(unit.code)
//Recursion happens here.
var result = await denestList(unit)
units.push(...result.allUnits)
artifacts.push(...result.allArtifacts)
}
return { allUnits: units, allArtifacts: artifacts }
}
The problem is that for (unit of incomingUnit.units) never happens. When I log unit.units it shows array full of IArtifact[], but when I run console.log(unit.units.length) it return 0.
Here is how to "denestList" function is called:
useEffect(() => {
asyncStart(unit)
}, []);
const asyncStart = async (mainUnit: IUnit) => {
var result = await denestList(mainUnit);
setAllUnits(result.allUnits)
setAllArtifacts(result.allArtifacts)
}
I would really appreciate any help. Thank you in advance
I have a couple of different functions that helps set different states of the component and i wish to run the functions in order of one another. I know that there are multiple posts on these but they seem to be mostly catered for running one function after the other but i have more than two functions that i need to execute in order
Desired order
1) Set state of starting and final destination
2) Run this.getDirections() (This function sets the state of arrOfPolylines which i desire to reset through resetRouteSelectionStatus())
3) Run resetRouteSelectionStatus()
4) After running these functions i wish to have an empty this.state.arrOfPolylines
Actual results
There is no error in the code but it isnt entering the resetRouteSelectionStatus() as none of the console log are printed. Can someone please guide me on the right path?
<Button
onPress={() => { //on button press set final destination and starting location
{
(this.state.tempDestination.longitude != null && this.state.tempStarting.longitude != null) &&
this.setState({
finalDestination: {
latitude: this.state.tempDestination.latitude,
longitude: this.state.tempDestination.longitude,
},
startingLocation: {
latitude: this.state.tempStarting.latitude,
longitude: this.state.tempStarting.longitude,
}
}, () => {
this.getDirections((this.state.startingLocation.latitude + "," + this.state.startingLocation.longitude), (this.state.finalDestination.latitude + "," + this.state.finalDestination.longitude),
() => {this.resetRouteSelectionStatus()});
}
);
}
}}
title="Determine Directions"
color="#00B0FF"
resetRouteSelectionStatus() {
console.log('entered reset route selection status function')
this.setState({arrOfPolyline: null }, () => {console.log("i should be null nd come first" + this.state.arrOfPolyline)}) ;
this.setState({ selectChallengeStatus: null });
this.setState({ userRouteSelectionStatus: null }); //when user click on button set seleection status to 0 so route options will be displayed again after generation new route
//this.setState({arrOfDirectionDetails: []}); // clear past direction details when user select generate route with new starting/ending location
// clear past poly lines when user has selected new routes
//console.log("everything has been reset");
}
async getDirections(startLoc, destinationLoc) {
let resp = await fetch(`https://maps.googleapis.com/maps/api/directions/json?origin=${startLoc}&destination=${destinationLoc}&key="KEY"&mode=driving&alternatives=true`)
let respJson = await resp.json();
let routeDetails = respJson.routes;
let tempPolyLineArray = [];
let tempDirArrayRoute = [];
//console.log(startLoc);
//console.log(destinationLoc);
for (i = 0; i < routeDetails.length; i++) // respJson.routes = number of alternative routes available
{
let tempDirArray = []; // at every new route, initalize a temp direction array
let points = Polyline.decode(respJson.routes[i].overview_polyline.points);
let coords = points.map((point, index) => {
return {
latitude: point[0],
longitude: point[1]
}
})
tempPolyLineArray.push(coords);
for (k = 0; k < routeDetails[i].legs[0].steps.length; k++) // for each route go to the correct route, enter legs ( always 0), get the number of instruction for this route
{
//console.log (routeDetails[i].legs[0].steps[k])
tempDirArray.push(routeDetails[i].legs[0].steps[k]) // push all instructions into temp direction array
//this.state.arrOfDirectionDetails.push(routeDetails[i].legs[0].steps[k]); // for each instruction save it to array
}
tempDirArrayRoute.push(tempDirArray); // at the end of each route, push all instructions stored in temp array as an array into state
}
this.setState({ arrOfDirectionDetails: tempDirArrayRoute });
this.setState({ arrOfPolyline: tempPolyLineArray });
//creating my html tags
let data = [];
let temptitle = "Route ";
for (let j = 0; j < routeDetails.length; j++) {
data.push(
<View key={j}>
<Button
title={temptitle + j}
onPress={() => this.updateUser(j)}
/>
</View>
)
}
this.setState({ routebox: data })
}
So a few things to note here:
I would extract the inline function for the onButtonPress function into an async function
Something along the lines of:
const onButtonPressHandler = async () => {
... put your code here
}
Then you'll modify your onButtonPress to something like:
<Button
onPress={this.onButtonPressHandler}
... rest of your props here
/>
You can then properly use async / await in the buttonPress handler.
The setState function is not a synchronous function. If you rely on the results right away you'll be disappointed.
Each time you call setState you could trigger a rerender.
I would instead merge all of your setState calls into a single at the end.
The getDirections function does not include the callback, while it should be:
async getDirections(startLoc, destinationLoc, callback) {
.....
this.setState({ routebox: data },callback());
}
or but not sure if it would be in order:
async () => {
await this.getDirections((this.state.startingLocation.latitude + "," + this.state.startingLocation.longitude), (this.state.finalDestination.latitude + "," + this.state.finalDestination.longitude) );
this.resetRouteSelectionStatus();
}
You might need edit the resetRouteSelectionStatus to be:
resetRouteSelectionStatus = async ()=>{
I've only been using JS and React for a short time, and am running into issues with waiting for a forEach loop to complete before continuing.
The function glitchLib below should pull an array of img sources from state, iterate through the elements of the array and "glitch" each image (the actual process of glitching is done with a javascript library). For each glitched image, I want to push a 2-elem array with the original source and glitched source into currentSaved[], and then pass the array of arrays in a callback.
glitchLib() {
const currentSaved = [];
var array = this.state.originalFiles;
array.forEach(function(src) {
var originalImage = src;
const image = new Image();
image.src = src;
image.onload = () => {
glitch()
.fromImage(image)
.toDataURL()
.then((dataURL) => {
const dataArray = [originalImage, dataURL];
currentSaved.push(dataArray);
});
};
});
this.props.callback(currentSaved);
}
If I wrap the callback in a setTimeout for ~10 seconds or so, the array is properly iterated through so there isn't any issue with the way the js library is performing the "glitching", which should just return a base64 image encoding. Without the setTimeout, an empty array is passed.
What is the proper way to wait for the array to be fully iterated through (or for that matter, is there any better way of doing this sort of thing)?
You can wait for the completion of a number of Promises using Promise.all():
const glitch = () => Promise.resolve('xyz')
function glitchLib(callback) {
const promises = []
const array = ['abc', 'def']
array.forEach(src => {
const originalImage = src
const image = new Image()
image.src = src
/*image.onload = */;(() => {
promises.push(
glitch()
//.fromImage(image)
//.toDataURL()
.then(dataURL => [originalImage, dataURL])
)
})()
})
Promise.all(promises)
.then(currentSaved => callback(currentSaved))
}
glitchLib(x => console.log(x))
i have angular 7 component which is tied to a model and there is an array inside that model, the array was populated from a service. and it's populated.
the problem is i can't map over the array although it has elements there.
when i console it it shows the array has element. then i tried to console typeOf(array) it always gives object although it is an array !!.
i tried using this soluation but it didn't help either.
any help please?
export class FooModel {
foo : Foo
bars: Bar[];
}
export class SomeComponent implements OnInit {
model: FooModel;
constructor(private service: ProjectService) {
this.model = new FooModel();
this.model.bars = [];
}
ngOnInit() {
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
});
Console.log(this.model); // the model has data at this point
const arr = this.model.bars.map(a=> {
// never comes here
return a;
});
console.log(arr); // nothing is displayed here
// this works why ??
const arr2 = [1,2,3].map(s=> {
return s;
}
console.log(arr2); // it displays [1,2,3]
}
}
As the request is asynchronous, you might need to place the logic within the subscribe,
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
const arr = this.model.bars.map(a=> {
// never comes here
return a;
});
console.log(arr);
});
subscription is asynchronous so while it is still working the next line operation in the execution stack will be performed in this case the map you have after the subscription meanwhile it is still being populated in the background. You can try mapping in another life cycle hook say viewChecked hopefully it works. #cheers
Please look at the comments
export class FooModel {
foo : Foo
bars: Bar[];
}
export class SomeComponent implements OnInit {
model: FooModel;
constructor(private service: ProjectService) {
this.model = new FooModel();
this.model.bars = [];
}
ngOnInit() {
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
});
// the following starts to execute even before the model is populated above.
const arr = this.model.bars.map(a=> {
// never comes here because this.model.bars is empty at here and the length is 0 and nothing inside map executes
return a;
});
console.log(arr); // nothing is displayed here because it has nothing inside
// this works why ?? because you are using on an array which has some items.
const arr2 = [1,2,3].map(s=> {
return s;
}
console.log(arr2); // it displays [1,2,3]
}
}
So as Sajeetharan suggested, you have keep it inside subscribe()
I am new to Angular and RxJS. I wonder what is the best/correct way to consume a value from an Observable multiple times.
My setup:
I have a component which calls a service which uses a REST service.
After the server returns the result I want to
use this result in the service at hand
AND return the result to the component.
// foo.component.ts
onEvent() {
this.fooService.foo()
.subscribe((data: FooStatus) => doSomething());
}
// foo.service.ts
private lastResult: FooStatus;
constructor(protected http: HttpClient) {
}
foo(): Observable<FooResult> {
return this.http.get('/foo')
.map((data: FooStatus) => {
this.lastResult = data; // use the data...
return data; // ... and simply pass it through
});
}
Using subscribe() multiple times will not work because the request would be send multiple times. This is wrong.
At the moment I use map() to intercept the result. But I am not comfortable with this because I introduce a side effect. Seems like a code smell to me.
I experimented with
foo(onSuccess: (result: FooResult) => void, onFailure: () => void): void {
...
}
but this looks even worse, I loose the Observable magic. And I do not want to have to write these callbacks in every service method myself.
As another way I considered the call to subscribe() in the service and then to create a fresh Observable I then can return to the component. But I could not get it work... seemed to complicated, too.
Is there a more elegant solution?
Is there a helpful method on Observable I did miss?
There are a number of ways to do this, and the answer will depend on your usage.
This codepen https://codepen.io/mikkel/pen/EowxjK?editors=0011
// interval observer
// click streams from 3 buttons
console.clear()
const startButton = document.querySelector('#start')
const stopButton = document.querySelector('#stop')
const resetButton = document.querySelector('#reset')
const start$ = Rx.Observable.fromEvent(startButton, 'click')
const stop$ = Rx.Observable.fromEvent(stopButton, 'click')
const reset$ = Rx.Observable.fromEvent(resetButton, 'click')
const minutes = document.querySelector('#minutes')
const seconds = document.querySelector('#seconds')
const milliseconds = document.querySelector('#milliseconds')
const toTime = (time) => ({
milliseconds: Math.floor(time % 100),
seconds: Math.floor((time/100) % 60),
minutes: Math.floor(time / 6000)
})
const pad = (number) => number <= 9 ? ('0' + number) : number.toString()
const render = (time) => {
minutes.innerHTML = pad(time.minutes)
seconds.innerHTML = pad(time.seconds)
milliseconds.innerHTML = pad(time.milliseconds)
}
const interval$ = Rx.Observable.interval(10)
const stopOrReset$ = Rx.Observable.merge(
stop$,
reset$
)
const pausible$ = interval$
.takeUntil(stopOrReset$)
const init = 0
const inc = acc => acc+1
const reset = acc => init
const incOrReset$ = Rx.Observable.merge(
pausible$.mapTo(inc),
reset$.mapTo(reset)
)
app$ = start$
.switchMapTo(incOrReset$)
.startWith(init)
.scan((acc, currFunc) => currFunc(acc))
.map(toTime)
.subscribe(val => render(val))
You will notice that the reset$ observable is used in two other observables, incOrReset$ and stopOrReset$
You can also introduce a .multicast() operator, which will explicitly allow you to subscribe multiple times. See description here: https://www.learnrxjs.io/operators/multicasting/
Take a look at share, it allows you to share a single subscription to the underlying source. You could do something like the following (it's not tested, but should give you the idea):
private lastResult: FooStatus;
private fooObs: Observable<FooStatus>;
constructor(protected http: HttpClient) {
this.fooObs = this.http.get('/foo').share();
this.fooObs.subscribe(((data: FooStatus) => this.lastResult = data);
}
foo(): Observable<FooResult> {
return this.fooObs.map(toFooResult);
}
as mentioned by #Mikkel in his answer that approach is cool however you can make use of following methods from Observable and achieve this.
these important methods are publishReplay and refCount,
use them as below,
private lastResult: FooStatus;
private myCachedObservable : Observble<FooResult> = null;
constructor(protected http: HttpClient) {
}
foo(): Observable<FooResult> {
if(!myCachedObservable){
this.myCachedObservable = this.http.get('/foo')
.map((data: FooStatus) => {
this.lastResult = data; // use the data...
return data; // ... and simply pass it through
})
.publishReplay(1)
.refCount();
return this.myObsResponse;
} else{
return this.myObsResponse;
}
}
Now in your component simply subscribe the observable returned from your method as above and notice the network request, you will see only one network request being made for this http call.