Repository: PistonDevelopers/glfw-rs (language: rust)

Issue:
Added render context for multi-threaded renders.
This adds the trait `Context` which can be used for rendering. There are two implementers of `Context` -- `Window` and `RenderContext`. A render context is made from a `Window` and allows for thread safe sharing of the `Window` between tasks.

The only ugly bit was dealing with dropping a `Window` that has an outstanding `RenderContext`. An internal channel is used in this case, the channel can only be fired when the `RenderContext` is dropped. If the user does not cleanup the the render task correctly it will cause the main thread to block waiting for a response after sending a warning message telling them they probably have a bug.



Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index d20ed39b..e16475b9 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,8 @@ GLFW bindings and wrapper for The Rust Programming Language.
 extern crate native;
 extern crate glfw;
 
+use glfw::Context;
+
 #[start]
 fn start(argc: int, argv: **u8) -> int {
     // Run GLFW on the main thread
diff --git a/src/examples/clipboard.rs b/src/examples/clipboard.rs
index 94b335c2..7e94e274 100644
--- a/src/examples/clipboard.rs
+++ b/src/examples/clipboard.rs
@@ -16,6 +16,8 @@
 extern crate native;
 extern crate glfw;
 
+use glfw::Context;
+
 #[start]
 fn start(argc: int, argv: **u8) -> int {
     native::start(argc, argv, main)
@@ -29,7 +31,7 @@ fn main() {
         .expect("Failed to create GLFW window.");
 
     window.set_key_polling(true);
-    window.make_context_current();
+    window.make_current();
     glfw.set_swap_interval(1);
 
     while !window.should_close() {
diff --git a/src/examples/cursor.rs b/src/examples/cursor.rs
index f97fad45..cd9396ac 100644
--- a/src/examples/cursor.rs
+++ b/src/examples/cursor.rs
@@ -16,6 +16,8 @@
 extern crate native;
 extern crate glfw;
 
+use glfw::Context;
+
 #[start]
 fn start(argc: int, argv: **u8) -> int {
     native::start(argc, argv, main)
@@ -29,7 +31,7 @@ fn main() {
         .expect("Failed to create GLFW window.");
 
     window.set_cursor_mode(glfw::CursorDisabled);
-    window.make_context_current();
+    window.make_current();
 
     window.set_cursor_pos_polling(true);
     window.set_key_polling(true);
diff --git a/src/examples/defaults.rs b/src/examples/defaults.rs
index a6a5cc59..0a16c0cf 100644
--- a/src/examples/defaults.rs
+++ b/src/examples/defaults.rs
@@ -16,6 +16,8 @@
 extern crate native;
 extern crate glfw;
 
+use glfw::Context;
+
 #[start]
 fn start(argc: int, argv: **u8) -> int {
     native::start(argc, argv, main)
@@ -30,7 +32,7 @@ fn main() {
     let (window, _) = glfw.create_window(640, 480, "Defaults", glfw::Windowed)
         .expect("Failed to create GLFW window.");
 
-    window.make_context_current();
+    window.make_current();
 
     let (width, height) = window.get_size();
     println!("window size: ({}, {})", width, height);
diff --git a/src/examples/events.rs b/src/examples/events.rs
index 9979a646..90d0fc3b 100644
--- a/src/examples/events.rs
+++ b/src/examples/events.rs
@@ -16,6 +16,8 @@
 extern crate native;
 extern crate glfw;
 
+use glfw::Context;
+
 #[start]
 fn start(argc: int, argv: **u8) -> int {
     native::start(argc, argv, main)
@@ -53,7 +55,7 @@ fn main() {
     // to poll in the above code.
     window.set_all_polling(true);
 
-    window.make_context_current();
+    window.make_current();
 
     while !window.should_close() {
         glfw.poll_events();
diff --git a/src/examples/render_task.rs b/src/examples/render_task.rs
new file mode 100644
index 00000000..fe1b7f5e
--- /dev/null
+++ b/src/examples/render_task.rs
@@ -0,0 +1,85 @@
+// Copyright 2014 The GLFW-RS Developers. For a full listing of the authors,
+// refer to the AUTHORS file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Demonstrates how concurrent rendering can be achieved
+//! through the use of `RenderContext`s.
+
+extern crate native;
+extern crate glfw;
+
+use glfw::Context;
+use std::task::task;
+
+#[start]
+fn start(argc: int, argv: **u8) -> int {
+    native::start(argc, argv, main)
+}
+
+fn main() {
+    let (glfw, errors) = glfw::init().unwrap();
+    glfw::fail_on_error(&errors);
+
+    let (mut window, events) = glfw.create_window(300, 300, "Hello this is window", glfw::Windowed)
+        .expect("Failed to create GLFW window.");
+
+    window.set_key_polling(true);
+
+    let render_context = window.render_context();
+    let (send, recv) = channel();
+
+    let mut render_task = task().named("render task");
+    let render_task_done = render_task.future_result();
+    render_task.spawn(proc() {
+        render(render_context, recv);
+    });
+
+    while !window.should_close() {
+        glfw.poll_events();
+        glfw::fail_on_error(&errors);
+        for (_, event) in glfw::flush_messages(&events) {
+            handle_window_event(&window, event);
+        }
+    }
+
+    // Tell the render task to exit.
+    send.send(());
+
+    // Wait for acknowledgement that the rendering was completed.
+    let _ = render_task_done.recv();
+}
+
+fn render(context: glfw::RenderContext, finish: Receiver<()>) {
+    context.make_current();
+    loop {
+        // Check if the rendering should stop.
+        if finish.try_recv() == std::comm::Data(()) { break };
+
+        // Perform rendering calls
+
+        context.swap_buffers();
+    }
+
+    // required on some platforms
+    glfw::make_context_current(None);
+}
+
+fn handle_window_event(window: &glfw::Window, event: glfw::WindowEvent) {
+    match event {
+        glfw::KeyEvent(glfw::KeyEscape, _, glfw::Press, _) => {
+            window.set_should_close(true)
+        }
+        _ => {}
+    }
+}
diff --git a/src/examples/title.rs b/src/examples/title.rs
index 2f332bbc..ea73d7f0 100644
--- a/src/examples/title.rs
+++ b/src/examples/title.rs
@@ -16,6 +16,8 @@
 extern crate native;
 extern crate glfw;
 
+use glfw::Context;
+
 #[start]
 fn start(argc: int, argv: **u8) -> int {
     native::start(argc, argv, main)
@@ -29,7 +31,7 @@ fn main() {
         .expect("Failed to create GLFW window.");
 
     window.set_key_polling(true);
-    window.make_context_current();
+    window.make_current();
     glfw.set_swap_interval(1);
 
     while !window.should_close() {
diff --git a/src/examples/window.rs b/src/examples/window.rs
index 25d8c7dc..9dd8a20e 100644
--- a/src/examples/window.rs
+++ b/src/examples/window.rs
@@ -16,6 +16,8 @@
 extern crate native;
 extern crate glfw;
 
+use glfw::Context;
+
 #[start]
 fn start(argc: int, argv: **u8) -> int {
     native::start(argc, argv, main)
@@ -29,7 +31,7 @@ fn main() {
         .expect("Failed to create GLFW window.");
 
     window.set_key_polling(true);
-    window.make_context_current();
+    window.make_current();
 
     while !window.should_close() {
         glfw.poll_events();
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
index e6b49eb2..f1018b24 100644
--- a/src/lib/lib.rs
+++ b/src/lib/lib.rs
@@ -21,6 +21,7 @@
 
 #![feature(globs)]
 #![feature(macro_rules)]
+#![feature(phase)]
 
 //! An ideomatic wrapper for the GLFW library.
 //!
@@ -29,6 +30,8 @@
 //! ~~~rust
 //! extern crate native;
 //! extern crate glfw;
+//!
+//! use glfw::Context;
 //! 
 //! #[start]
 //! fn start(argc: int, argv: **u8) -> int {
@@ -45,7 +48,7 @@
 //!         .expect("Failed to create GLFW window.");
 //! 
 //!     // Make the window's context current
-//!     window.make_context_current();
+//!     window.make_current();
 //! 
 //!     // Loop until the user closes the window
 //!     while !window.should_close() {
@@ -72,6 +75,7 @@
 
 extern crate semver;
 extern crate sync;
+#[phase(syntax, link)] extern crate log;
 
 use std::cast;
 use std::comm::{channel, Receiver, Sender, Data};
@@ -501,6 +505,7 @@ impl Glfw {
         if ptr.is_null() {
             None
         } else {
+            let (drop_sender, drop_receiver) = channel();
             let (sender, receiver) = channel();
             unsafe { ffi::glfwSetWindowUserPointer(ptr, cast::transmute(~sender)); }
             Some((
@@ -508,6 +513,8 @@ impl Glfw {
                     ptr: ptr,
                     glfw: self.clone(),
                     is_shared: share.is_none(),
+                    drop_sender: Some(drop_sender),
+                    drop_receiver: drop_receiver
                 },
                 receiver,
             ))
@@ -1011,11 +1018,17 @@ impl<'a, Message: Send> Iterator<Message> for FlushedMessages<'a, Message> {
     }
 }
 
+/// A message for notifying a `Window` that a `RenderContext` has been dropped.
+#[deriving(Eq)]
+struct ContextDropped;
+
 /// A struct that wraps a `*GLFWwindow` handle.
 pub struct Window {
     pub ptr: *ffi::GLFWwindow,
     pub glfw: Glfw,
     pub is_shared: bool,
+    drop_sender: Option<Sender<ContextDropped>>,
+    drop_receiver: Receiver<ContextDropped>
 }
 
 macro_rules! set_window_callback(
@@ -1038,6 +1051,16 @@ impl Window {
     /// window.
     pub fn close(self) {}
 
+    /// Returns a render context that can be shared between tasks, allowing
+    /// for concurrent rendering.
+    pub fn render_context(&mut self) -> RenderContext {
+        RenderContext {
+            ptr: self.ptr,
+            // this will only be None after dropping so this is safe
+            drop_sender: self.drop_sender.as_ref().unwrap().clone()
+        }
+    }
+
     /// Wrapper for `glfwWindowShouldClose`.
     pub fn should_close(&self) -> bool {
         unsafe { ffi::glfwWindowShouldClose(self.ptr) == ffi::TRUE }
@@ -1348,25 +1371,6 @@ impl Window {
         unsafe { str::raw::from_c_str(ffi::glfwGetClipboardString(self.ptr)) }
     }
 
-    /// Wrapper for `glfwMakeContextCurrent`.
-    pub fn make_context_current(&self) {
-        self.glfw.make_context_current(Some(self));
-    }
-
-    /// Returns `true` if the window is the current context.
-    pub fn is_current_context(&self) -> bool {
-        self.ptr == unsafe { ffi::glfwGetCurrentContext() }
-    }
-
-    /// Swaps the front and back buffers of the window. If the swap interval is
-    /// greater than zero, the GPU driver waits the specified number of screen
-    /// updates before swapping the buffers.
-    ///
-    /// Wrapper for `glfwSwapBuffers`.
-    pub fn swap_buffers(&self) {
-        unsafe { ffi::glfwSwapBuffers(self.ptr); }
-    }
-
     /// Wrapper for `glfwGetWin32Window`
     #[cfg(target_os="win32")]
     pub fn get_win32_window(&self) -> *c_void {
@@ -1406,10 +1410,19 @@ impl Window {
 
 #[unsafe_destructor]
 impl Drop for Window {
-    /// Closes the window and performs the necessary cleanups.
+    /// Closes the window and performs the necessary cleanups. This will block
+    /// until all associated `RenderContext`s were also dropped.
     ///
     /// Wrapper for `glfwDestroyWindow`.
     fn drop(&mut self) {
+        drop(self.drop_sender.take());
+
+        if self.drop_receiver.try_recv() != std::comm::Disconnected {
+            error!("Attempted to drop a Window before the `RenderContext` was dropped.");
+            error!("Blocking until the `RenderContext` was dropped.");
+            let _ = self.drop_receiver.recv_opt();
+        }
+
         if !self.is_shared {
             unsafe { ffi::glfwDestroyWindow(self.ptr); }
         }
@@ -1421,6 +1434,55 @@ impl Drop for Window {
     }
 }
 
+/// A rendering context that can be shared between tasks.
+pub struct RenderContext {
+    ptr: *ffi::GLFWwindow,
+    drop_sender: Sender<ContextDropped>
+}
+
+/// Methods common to renderable contexts
+pub trait Context {
+    /// Returns the pointer to the underlying `GLFWwindow`.
+    fn window_ptr(&self) -> *ffi::GLFWwindow;
+
+    /// Swaps the front and back buffers of the window. If the swap interval is
+    /// greater than zero, the GPU driver waits the specified number of screen
+    /// updates before swapping the buffers.
+    ///
+    /// Wrapper for `glfwSwapBuffers`.
+    fn swap_buffers(&self) {
+        let ptr = self.window_ptr();
+        unsafe { ffi::glfwSwapBuffers(ptr); }
+    }
+
+    /// Returns `true` if the window is the current context.
+    fn is_current(&self) -> bool {
+        self.window_ptr() == unsafe { ffi::glfwGetCurrentContext() }
+    }
+
+    /// Wrapper for `glfwMakeContextCurrent`
+    fn make_current(&self) {
+        let ptr = self.window_ptr();
+        unsafe { ffi::glfwMakeContextCurrent(ptr); }
+    }
+}
+
+impl Context for Window {
+    fn window_ptr(&self) -> *ffi::GLFWwindow { self.ptr }
+}
+
+impl Context for RenderContext {
+    fn window_ptr(&self) -> *ffi::GLFWwindow { self.ptr }
+}
+
+/// Wrapper for `glfwMakeContextCurrent`.
+pub fn make_context_current(context: Option<&Context>) {
+    match context {
+        Some(ctx) => unsafe { ffi::glfwMakeContextCurrent(ctx.window_ptr()) },
+        None      => unsafe { ffi::glfwMakeContextCurrent(ptr::null()) },
+    }
+}
+
 /// Joystick identifier tokens.
 #[repr(C)]
 #[deriving(Clone, Eq, Hash, Show)]


Test patch: