Repository: brendanzab/gl-rs (language: rust)

Issue:
Add fallbacks for identical but renamed functions
Many functions exist as `glFooEXT` or `glFooARB` before being named `glFoo`. There's a C library that does this, and I thought it was Waffle, but it isn't, so I'm not sure what the prior art is. We'll probably need to curate our own mapping. I'm working on this.



Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 9a97c461..1ac78352 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,12 @@ if gl::Viewport::is_loaded() {
 }
 ~~~
 
+## Extra features
+
+The global and struct generators will attempt to use fallbacks functions when
+they are available. For example, if `glGenFramebuffers` cannot be loaded it will
+also attempt to load `glGenFramebuffersEXT` as a fallback.
+
 ## Using gl_generator
 
 If you need a specific version of OpenGL, or you need a different API
@@ -133,6 +139,8 @@ The struct generator is a cleaner alternative to the global generator.
 The main difference is that you must call `gl::Gl::load_with` instead of
 `gl::load_with`, and this functions returns a struct of type `Gl`. The OpenGL
 functions are not static functions but member functions in this `Gl` struct.
+This is important when the GL functions are associated with the current
+context, as is true on Windows.
 
 The enumerations and types are still static and available in a similar way as
 in the global generator.
diff --git a/src/gl_generator/generators/global_gen.rs b/src/gl_generator/generators/global_gen.rs
index 8bb34601..4bf1b033 100644
--- a/src/gl_generator/generators/global_gen.rs
+++ b/src/gl_generator/generators/global_gen.rs
@@ -27,6 +27,7 @@ impl super::Generator for GlobalGenerator {
     fn write(&self, ecx: &ExtCtxt, registry: &Registry, ns: Ns) -> Vec<P<ast::Item>> {
         let mut result = Vec::new();
         result.push(write_header(ecx));
+        result.push(write_metaloadfn(ecx));
         result.push(write_type_aliases(ecx, &ns));
         result.extend(write_enums(ecx, registry).into_iter());
         result.extend(write_fns(ecx, registry).into_iter());
@@ -50,6 +51,24 @@ fn write_header(ecx: &ExtCtxt) -> P<ast::Item> {
     )).unwrap()
 }
 
+/// Creates the metaloadfn function for fallbacks
+fn write_metaloadfn(ecx: &ExtCtxt) -> P<ast::Item> {
+    (quote_item!(ecx,
+        fn metaloadfn(loadfn: |&str| -> *const __gl_imports::libc::c_void,
+                      symbol: &str,
+                      fallbacks: &[&str]) -> *const __gl_imports::libc::c_void {
+            let mut ptr = loadfn(symbol);
+            if ptr.is_null() {
+                for &sym in fallbacks.iter() {
+                    ptr = loadfn(sym);
+                    if !ptr.is_null() { break; }
+                }
+            }
+            ptr
+        }
+    )).unwrap()
+}
+
 /// Creates a `types` module which contains all the type aliases.
 ///
 /// See also `generators::gen_type_aliases`.
@@ -83,16 +102,22 @@ fn write_fns(ecx: &ExtCtxt, registry: &Registry) -> Vec<P<ast::Item>> {
     registry.cmd_iter().map(|c| {
         use syntax::ext::quote::rt::ToSource;
 
+        let doc = match registry.aliases.find(&c.proto.ident) {
+            Some(v) => format!("/** Fallbacks: {} */", v.connect(", ")),
+            None => "".to_string()
+        };
+
         ecx.parse_item(if c.is_safe {
             format!(
                 "#[allow(non_snake_case)] #[allow(unused_variables)] #[allow(dead_code)]
-                #[inline] #[unstable] pub fn {name}({params}) -> {return_suffix} {{ \
+                #[inline] #[unstable] {doc} pub fn {name}({params}) -> {return_suffix} {{ \
                     unsafe {{ \
                         __gl_imports::mem::transmute::<_, extern \"system\" fn({types}) -> {return_suffix}>\
                             (storage::{name}.f)({idents}) \
                     }} \
                 }}",
                 name = c.proto.ident,
+                doc = doc,
                 params = super::gen_parameters(ecx, c).into_iter().map(|p| p.to_source()).collect::<Vec<String>>().connect(", "),
                 types = super::gen_parameters(ecx, c).into_iter().map(|p| p.ty.to_source()).collect::<Vec<String>>().connect(", "),
                 return_suffix = super::gen_return_type(ecx, c).to_source(),
@@ -101,11 +126,12 @@ fn write_fns(ecx: &ExtCtxt, registry: &Registry) -> Vec<P<ast::Item>> {
         } else {
             format!(
                 "#[allow(non_snake_case)] #[allow(unused_variables)] #[allow(dead_code)]
-                #[inline] #[unstable] pub unsafe fn {name}({typed_params}) -> {return_suffix} {{ \
+                #[inline] #[unstable] {doc} pub unsafe fn {name}({typed_params}) -> {return_suffix} {{ \
                     __gl_imports::mem::transmute::<_, extern \"system\" fn({typed_params}) -> {return_suffix}>\
                         (storage::{name}.f)({idents}) \
                 }}",
                 name = c.proto.ident,
+                doc = doc,
                 typed_params = super::gen_parameters(ecx, c).into_iter().map(|p| p.to_source()).collect::<Vec<String>>().connect(", "),
                 return_suffix = super::gen_return_type(ecx, c).to_source(),
                 idents = super::gen_parameters(ecx, c).into_iter().map(|p| p.pat.to_source()).collect::<Vec<String>>().connect(", "),
@@ -172,15 +198,22 @@ fn write_ptrs(ecx: &ExtCtxt, registry: &Registry) -> P<ast::Item> {
 ///  created by `write_ptrs`.
 fn write_fn_mods(ecx: &ExtCtxt, registry: &Registry, ns: &Ns) -> Vec<P<ast::Item>> {
     registry.cmd_iter().map(|c| {
+        let fallbacks = match registry.aliases.find(&c.proto.ident) {
+            Some(v) => {
+                let names = v.iter().map(|name| format!("\"{}\"", super::gen_symbol_name(ns, name.as_slice()))).collect::<Vec<_>>();
+                format!("[{}]", names.connect(", "))
+            }, None => "[]".to_string(),
+        };
+        let fallbacks = ecx.parse_expr(fallbacks);
         let fnname = ecx.ident_of(c.proto.ident.as_slice());
-        let symbol = super::gen_symbol_name(ns, c);
+        let symbol = super::gen_symbol_name(ns, c.proto.ident.as_slice());
         let symbol = symbol.as_slice();
 
         (quote_item!(ecx,
             #[unstable]
             #[allow(non_snake_case)]
             pub mod $fnname {
-                use super::{failing, storage};
+                use super::{failing, storage, metaloadfn};
                 use super::FnPtr;
 
                 #[inline]
@@ -192,7 +225,7 @@ fn write_fn_mods(ecx: &ExtCtxt, registry: &Registry, ns: &Ns) -> Vec<P<ast::Item
                 #[allow(dead_code)]
                 pub fn load_with(loadfn: |symbol: &str| -> *const super::__gl_imports::libc::c_void) {
                     unsafe {
-                        storage::$fnname = FnPtr::new(loadfn($symbol),
+                        storage::$fnname = FnPtr::new(metaloadfn(loadfn, $symbol, $fallbacks),
                             failing::$fnname as *const super::__gl_imports::libc::c_void)
                     }
                 }
diff --git a/src/gl_generator/generators/mod.rs b/src/gl_generator/generators/mod.rs
index 18ce64c2..de621fe4 100644
--- a/src/gl_generator/generators/mod.rs
+++ b/src/gl_generator/generators/mod.rs
@@ -149,11 +149,11 @@ pub fn gen_return_type(ecx: &ExtCtxt, cmd: &Cmd) -> P<ast::Ty> {
 /// Generates the native symbol name of a `Cmd`.
 ///
 /// Example results: `"glClear"`, `"wglCreateContext"`, etc.
-pub fn gen_symbol_name(ns: &Ns, cmd: &Cmd) -> String {
+pub fn gen_symbol_name(ns: &Ns, cmd: &str) -> String {
     (match *ns {
         Gl | Gles1 | Gles2 => "gl",
         Glx => "glX",
         Wgl => "wgl",
         Egl => "egl",
-    }).to_string() + cmd.proto.ident.as_slice()
+    }).to_string() + cmd
 }
diff --git a/src/gl_generator/generators/static_gen.rs b/src/gl_generator/generators/static_gen.rs
index aae78da6..e5c0b21b 100644
--- a/src/gl_generator/generators/static_gen.rs
+++ b/src/gl_generator/generators/static_gen.rs
@@ -80,7 +80,7 @@ fn write_fns(ecx: &ExtCtxt, registry: &Registry, ns: &Ns) -> P<ast::Item> {
         format!(
             "#[link_name=\"{symbol}\"]
             pub fn {name}({params}) -> {return_suffix};",
-            symbol = super::gen_symbol_name(ns, c),
+            symbol = super::gen_symbol_name(ns, c.proto.ident.as_slice()),
             name = c.proto.ident,
             params = super::gen_parameters(ecx, c).into_iter().map(|p| p.to_source()).collect::<Vec<String>>().connect(", "),
             return_suffix = super::gen_return_type(ecx, c).to_source()
diff --git a/src/gl_generator/generators/struct_gen.rs b/src/gl_generator/generators/struct_gen.rs
index 5d3ac907..50333cc7 100644
--- a/src/gl_generator/generators/struct_gen.rs
+++ b/src/gl_generator/generators/struct_gen.rs
@@ -157,8 +157,13 @@ fn write_struct(ecx: &ExtCtxt, registry: &Registry, ns: &Ns) -> P<ast::Item> {
 
         ns = *ns,
         ptrs = registry.cmd_iter().map(|c| {
+            let fallbacks = match registry.aliases.find(&c.proto.ident) {
+                Some(v) => v.clone(),
+                None => Vec::new(),
+            };
             format!(
-                "pub {name}: FnPtr,",
+                "{doc} pub {name}: FnPtr,",
+                doc = if fallbacks.len() == 0 { "".to_string() } else { format!("/** Fallbacks: {} */", fallbacks.connect(", ")) },
                 name = c.proto.ident,
             )
         }).collect::<Vec<String>>().connect("\n")
@@ -179,6 +184,16 @@ fn write_impl(ecx: &ExtCtxt, registry: &Registry, ns: &Ns) -> P<ast::Item> {
             #[allow(dead_code)]
             #[allow(unused_variables)]
             pub fn load_with(loadfn: |symbol: &str| -> *const __gl_imports::libc::c_void) -> {ns:c} {{
+                let metaloadfn: |&str, &[&str]| -> *const __gl_imports::libc::c_void = |symbol, symbols| {{
+                    let mut ptr = loadfn(symbol);
+                    if ptr.is_null() {{
+                        for &sym in symbols.iter() {{
+                            ptr = loadfn(sym);
+                            if !ptr.is_null() {{ break; }}
+                        }}
+                    }}
+                    ptr
+                }};
                 {ns:c} {{
                     {loadings}
                 }}
@@ -190,10 +205,15 @@ fn write_impl(ecx: &ExtCtxt, registry: &Registry, ns: &Ns) -> P<ast::Item> {
         ns = *ns,
 
         loadings = registry.cmd_iter().map(|c| {
+            let fallbacks = registry.aliases.find(&c.proto.ident);
             format!(
-                "{name}: FnPtr::new(loadfn(\"{symbol}\"), failing::{name} as *const __gl_imports::libc::c_void),",
+                "{name}: FnPtr::new(metaloadfn(\"{symbol}\", {fb}), failing::{name} as *const __gl_imports::libc::c_void),",
                 name = c.proto.ident,
-                symbol = super::gen_symbol_name(ns, c)
+                symbol = super::gen_symbol_name(ns, c.proto.ident.as_slice()),
+                fb = match fallbacks {
+                    Some(fallbacks) => format!("[{}]", fallbacks.iter().map(|name| format!("\"{}\"", name)).collect::<Vec<_>>().connect(", ")),
+                    None => format!("[]")
+                },
             )
         }).collect::<Vec<String>>().connect("\n"),
 
diff --git a/src/gl_generator/registry.rs b/src/gl_generator/registry.rs
index c58e40fe..4ffd40a3 100644
--- a/src/gl_generator/registry.rs
+++ b/src/gl_generator/registry.rs
@@ -18,9 +18,11 @@
 extern crate collections;
 extern crate xml;
 
+use std::collections::hash_map::{Vacant, Occupied};
 use self::collections::TreeSet;
 use std::cell::RefCell;
 use std::collections::HashSet;
+use std::collections::HashMap;
 use std::fmt;
 use std::from_str::FromStr;
 use std::slice::Items;
@@ -91,12 +93,22 @@ fn trim_cmd_prefix<'a>(ident: &'a str, ns: Ns) -> &'a str {
     }
 }
 
+fn merge_map(a: &mut HashMap<String, Vec<String>>, b: HashMap<String, Vec<String>>) {
+    for (k, v) in b.into_iter() {
+        match a.entry(k) {
+            Occupied(mut ent) => { ent.get_mut().extend(v.into_iter()); },
+            Vacant(ent) => { ent.set(v); }
+        }
+    }
+}
+
 pub struct Registry {
     pub groups: Vec<Group>,
     pub enums: Vec<Enum>,
     pub cmds: Vec<Cmd>,
     pub features: Vec<Feature>,
     pub extensions: Vec<Extension>,
+    pub aliases: HashMap<String, Vec<String>>,
 }
 
 impl Registry {
@@ -334,6 +346,7 @@ impl<'a, R: Buffer> RegistryBuilder<R> {
             cmds: Vec::new(),
             features: Vec::new(),
             extensions: Vec::new(),
+            aliases: HashMap::new(),
         };
 
         loop {
@@ -369,7 +382,9 @@ impl<'a, R: Buffer> RegistryBuilder<R> {
 
                 // add command namespace
                 events::StartElement{ref name, ..} if name.local_name.as_slice() == "commands" => {
-                    registry.cmds.extend(self.consume_cmds().into_iter());
+                    let (cmds, aliases) = self.consume_cmds();
+                    registry.cmds.extend(cmds.into_iter());
+                    merge_map(&mut registry.aliases, aliases);
                 }
 
                 events::StartElement{ref name, ref attributes, ..} if name.local_name.as_slice() == "feature" => {
@@ -400,7 +415,7 @@ impl<'a, R: Buffer> RegistryBuilder<R> {
         match self.filter {
             Some(ref filter) => {
                 let Registry {
-                    groups, enums, cmds, features: feats, extensions: exts
+                    groups, enums, cmds, aliases, features: feats, extensions: exts,
                 } = registry;
 
                 let mut desired_enums = HashSet::new();
@@ -473,6 +488,7 @@ impl<'a, R: Buffer> RegistryBuilder<R> {
                     // these aren't important after this step
                     features: Vec::new(),
                     extensions: Vec::new(),
+                    aliases: aliases,
                 }
             },
             None => registry
@@ -577,13 +593,24 @@ impl<'a, R: Buffer> RegistryBuilder<R> {
         enums
     }
 
-    fn consume_cmds(&self) -> Vec<Cmd> {
+    fn consume_cmds(&self) -> (Vec<Cmd>, HashMap<String, Vec<String>>) {
         let mut cmds = Vec::new();
+        let mut aliases: HashMap<String, Vec<String>> = HashMap::new();
         loop {
             match self.recv() {
                 // add command definition
                 events::StartElement{ref name, ..} if name.local_name.as_slice() == "command" => {
-                    cmds.push(self.consume_cmd());
+                    let new = self.consume_cmd();
+                    match new.alias {
+                        Some(ref v) => {
+                            match aliases.entry(v.clone()) {
+                                Occupied(mut ent) => { ent.get_mut().push(new.proto.ident.clone()); },
+                                Vacant(ent) => { ent.set(vec![new.proto.ident.clone()]); }
+                            }
+                        },
+                        None => { }
+                    }
+                    cmds.push(new);
                 }
                 // finished building the namespace
                 events::EndElement{ref name} if name.local_name.as_slice() == "commands" => break,
@@ -591,7 +618,7 @@ impl<'a, R: Buffer> RegistryBuilder<R> {
                 msg => panic!("Expected </commands>, found: {}", msg.to_string()),
             }
         }
-        cmds
+        (cmds, aliases)
     }
 
     fn consume_cmd(&self) -> Cmd {
@@ -612,7 +639,8 @@ impl<'a, R: Buffer> RegistryBuilder<R> {
                     );
                 }
                 events::StartElement{ref name, ref attributes, ..} if name.local_name.as_slice() == "alias" => {
-                    alias = get_attribute(attributes.as_slice(), "alias");
+                    alias = get_attribute(attributes.as_slice(), "name");
+                    alias = alias.map(|t| trim_cmd_prefix(t.as_slice(), self.ns).to_string());
                     self.expect_end_element("alias");
                 }
                 events::StartElement{ref name, ref attributes, ..} if name.local_name.as_slice() == "vecequiv" => {


Test patch:
diff --git a/tests/gl_symbols.rs b/tests/gl_symbols.rs
index 67c9bade..fee7def3 100644
--- a/tests/gl_symbols.rs
+++ b/tests/gl_symbols.rs
@@ -1,13 +1,11 @@
-//! This file contains a single test that is
-//!  compiled but not run, just to ensure that
-//!  the GL symbols are defined
+//! This test ensures that the GL symbols are defined and that fallback works correctly.
 
 extern crate gl;
 extern crate libc;
 
 #[test]
 #[ignore]
-fn test() {
+fn symbols_exist() {
 	gl::Clear(gl::COLOR_BUFFER_BIT);
 	let _: libc::c_uint = gl::CreateProgram();
 	gl::CompileShader(5);
@@ -18,3 +16,16 @@ fn test() {
     }
 }
 
+#[test]
+fn fallback_works() {
+    fn loader(name: &str) -> *const libc::c_void {
+        match name {
+            "glGenFramebuffers" => 0 as *const libc::c_void,
+            "glGenFramebuffersEXT" => 42 as *const libc::c_void,
+            name => panic!("test tried to load {} unexpectedly!", name)
+        }
+    };
+
+    gl::GenFramebuffers::load_with(loader);
+    assert!(gl::GenFramebuffers::is_loaded());
+}