-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can't share window between threads #177
Comments
Any reason you are moving the window into the scoped thread, instead of taking a mutable reference? |
@crumblingstatue Ah, that's fixed it thank you. The reason I wasn't passing a mutable reference is because I'm rather new to Rust :) However, due to crossbeam's thread joining, we're not actually getting to the display functionality immediately here, just joining the input thread, and then running the render call. If I try to do something like this: extern crate sfml;
extern crate crossbeam;
use std::{time, thread};
use sfml::window::{ContextSettings, Style, Event, Key};
use sfml::graphics::{RenderWindow};
fn input(_scope: &crossbeam::Scope, window: &mut RenderWindow) {
while let &Some(event) = &window.wait_event() {
println!("{:?}", event);
match event {
Event::Closed |
Event::KeyPressed {
code: Key::Escape, ..
} => break,
_ => {}
}
}
}
fn render(_scope: &crossbeam::Scope, window: &mut RenderWindow) {
loop {
println!("In render loop!");
thread::sleep(time::Duration::from_millis(1000));
}
}
fn main() {
let mut main_window = RenderWindow::new(
(800, 500),
"Test window",
Style::CLOSE,
&ContextSettings { ..Default::default() }
);
main_window.set_vertical_sync_enabled(true);
crossbeam::scope(|scope| {
let input_thread = scope.spawn(|| { input(scope, &mut main_window) });
let render_thread = scope.spawn(|| { render(scope, &mut main_window) });
});
main_window.display()
} We're now failing with: 40 | let input_thread = scope.spawn(|| { input(scope, &mut main_window) });
| ^^^^^ `*mut csfml_graphics_sys::sfRenderWindow`
| cannot be sent between threads safely
|
= help: within `sfml::graphics::RenderWindow`, the trait `std::marker::Sync`
is not implemented for `*mut csfml_graphics_sys::sfRenderWindow`
= note: required because it appears within the type `sfml::graphics::RenderWindow`
= note: required because of the requirements on the impl of `std::marker::Send` for
`&sfml::graphics::RenderWindow`
= note: required because it appears within the type
`[closure@src/main.rs:40:40: 40:73
scope:&&crossbeam::Scope<'_>, main_window:&sfml::graphics::RenderWindow]` Any ideas? |
I'm actually not sure if it really is safe to send a In the meantime, I'm not sure what advice to give, other than try not to have input handling on a different thread. It's usually not required. |
Thanks for your help! Coming from C++, |
That SO article is talking about SDL. Are you sure this also applies to SFML? I never had any sluggish input with SFML (But then again, not with SDL either), even with single threaded event handling. |
You may be right, I've not worked with SFML before this. I can use while let Some(event) = window.poll_event() { ... } Which could take a long time (relatively) if I've not polled this frame and the user has been pressing lots of keys. I'll make some tests just now and see how it performs. |
So I ran some tests using poll input, and the results were as follows: I ran the same test 3 times, for the tests I used only the keys
So while 0.23ms is really fast enough, 4.78ms starts to eat into our 16ms/frame render cycle for 60FPS. Here's the code I used to extract the data to JSON format: extern crate sfml;
extern crate crossbeam;
use std::time;
use std::fs::File;
use std::io::Write;
use std::ops::{Add, Div};
use sfml::window::{ContextSettings, Style, Event, Key};
use sfml::graphics::{RenderWindow};
fn input(window: &mut RenderWindow) -> bool {
while let &Some(event) = &window.poll_event() {
match event {
Event::Closed |
Event::KeyPressed {
code: Key::Escape, ..
} => return true,
_ => {}
}
}
return false;
}
fn main_loop(window: &mut RenderWindow) {
// Count frames
let mut counter = 0;
let mut is_first = true;
let mut max_input = time::Duration::from_millis(0);
let mut max_render = time::Duration::from_millis(0);
let mut total_input_time = time::Duration::from_millis(0);
let mut total_render_time = time::Duration::from_millis(0);
let mut f = File::create("input_states.json").expect("Unable to create file");
let mut write_to_file = |string: String| -> () {f.write_all(string.as_bytes()).expect("Unable to write data")};
// Opening json brace
write_to_file("[".to_string());
loop {
let start = time::Instant::now();
if input(window) == true {
break;
};
let time_after_input = start.elapsed();
// Re-render the screen
window.display();
let time_after_render = start.elapsed();
// Now we've got the times ASAP, run slower processing
total_input_time = total_input_time.add(time_after_input);
total_render_time = total_render_time.add(time_after_render);
// Increment our max counter if needed
if time_after_input.gt(&max_input) {
max_input = time_after_input;
}
// Increment our max counter if needed
if time_after_render.gt(&max_render) {
max_render = time_after_render;
}
counter += 1;
if counter > 10 {
let avg_input = total_input_time.div(counter).subsec_nanos();
let avg_render = total_render_time.div(counter).subsec_nanos();
let mut to_write = "".to_string();
if is_first == false {
to_write.push_str(",\r\n");
}
to_write.push_str("{");
to_write.push_str("\"input\":");
to_write.push_str(&avg_input.to_string());
to_write.push_str(", \"render\":");
to_write.push_str(&avg_render.to_string());
to_write.push_str("}");
write_to_file(to_write);
// Zero everything out
counter = 0;
total_input_time = time::Duration::from_millis(0);
total_render_time = time::Duration::from_millis(0);
is_first = false;
}
}
// Closing json brace
write_to_file("]".to_string());
println!("--------------------------------------------------");
println!("Finished:");
println!("Input time MAX: {:?}", max_input);
println!("Render time MAX: {:?}", max_render);
}
fn main() {
let mut main_window = RenderWindow::new(
(800, 500),
"Test window",
Style::CLOSE,
&ContextSettings { ..Default::default() }
);
main_loop(&mut main_window);
} And the python code for formatting the JSON: import math
import json
with open("input_states.json") as fin:
data = json.loads(fin.read());
inputs = ()
renders = ()
for item in data:
inputs += (item['input'],)
renders += (item['render'],)
avgInput = sum(inputs) / len(data)
avgRender = sum(renders) / len(data)
maxInput = max(inputs)
maxRender = max(renders)
print("Item | Time taken (ns) | Time taken (ms)")
print("------------ | ------------- | -------------")
print("Max input latency |", math.floor(maxInput), "|", math.floor(maxInput / 1e4) / 100)
print("Max render latency |", math.floor(maxRender), "|", math.floor(maxRender / 1e4) / 100)
print("Average input latency |", math.floor(avgInput), "|", math.floor(avgInput / 1e4) / 100)
print("Average render latency |", math.floor(avgRender), "|", math.floor(avgRender / 1e4) / 100) |
Looks like maybe only the first render & input are causing the problem. Removing the first input timing, causes a reduction down to
|
this may be noteworthy:
from this tutorial on the SFML website |
So, how to renderer in loop, if |
Not sure what you're asking here. If you're already doing all the rendering within the loop, why is it a problem if it blocks the main thread? |
But I want to execute code after loop |
@DuckerMan |
Ok, thank you very much 😃 |
I managed to do this in code but, it isn't very clean code: use sfml::window::*;
use sfml::graphics::*;
use std::thread;
use crate::input::InputHandler;
use std::sync::{Mutex, Arc};
use std::time::{SystemTime, Duration};
use std::thread::sleep;
mod input;
mod entity;
mod component;
unsafe fn render_thread(mut window: WindowBox) {
(*window.0).set_vertical_sync_enabled(true);
(*window.0).set_active(true);
while (*window.0).is_open() {
let now = SystemTime::now();
(*window.0).clear(Color::BLACK);
// draw calls
(*window.0).display();
println!("FPS: {:.2}", 1e9/now.elapsed().unwrap().as_nanos() as f32)
}
}
struct WindowBox(*mut RenderWindow);
unsafe impl Send for WindowBox {}
unsafe impl Sync for WindowBox {}
fn main() {
let mut window = RenderWindow::new(
(800, 600),
"SFML works!",
Style::CLOSE | Style::RESIZE,
&Default::default()
);
window.set_active(false);
let mut wb = WindowBox(&mut window as *mut _);
let rthread = thread::spawn(
|| unsafe{
render_thread(wb)
});
let mut input_handler = InputHandler::new(0);
while window.is_open() {
let now = SystemTime::now();
while let Some(event) = window.poll_event() {
match event {
Event::Closed | Event::KeyPressed { code: Key::Escape, .. } => return,
_ => {}
}
}
input_handler.handle_input();
sleep(Duration::from_millis(10));
println!("TPS: {:.2}", 1000./now.elapsed().unwrap().as_millis() as f32);
}
rthread.join();
} |
@BinaryAura How can you be sure that no race conditions occur? Also from what I know, rendering with opengl only works on the main thread, so if my information is correct that code shouldn't work. |
@MagicRB You're right. OpenGL can be rendered only in the main thread |
Oh and for those wanting to share windows between threads, command buffers might be of use to you folk |
@MagicRB. Technecally, you can't. To do this properly would require breaking up the RenderWindow object into three parts. The event loop actions (only main), the render actions (only Render), World (a.k.a. objects to draw) (mut in main and ref in Render). As for OpenGL, it's not that it can only be done in the main thread. OpenGL has to have the current context owned by the running thread. Of coarse to fix this would be a total pain. |
Well, if you want to control some parts of the window, like resizing and stuff, a command buffer can be used |
While attempting to setup a threaded render-input system, I ran across the following:
Which gives the rustc error:
Can you suggest how I could get access to the window for both rendering and input processing?
I tried
Arc::new(main_window)
and it's giving me the same issue.The text was updated successfully, but these errors were encountered: