piston-dyon_interactive 0.37.0

A library for interactive coding with the Piston game engine
Documentation
#[macro_use]
extern crate dyon;
extern crate sdl2_window;
extern crate opengl_graphics;
extern crate piston;
extern crate current;
extern crate dyon_interactive;
extern crate music;
extern crate image;

use std::sync::Arc;
use current::CurrentGuard;
use dyon::{error, load, Module, Dfn, Runtime, Type};
use dyon_interactive::{FontNames, ImageNames};
use image::RgbaImage;
use piston::input::Event;
use piston::window::WindowSettings;
use piston::event_loop::{Events, EventSettings};
use sdl2_window::Sdl2Window;
use opengl_graphics::{OpenGL, Filter, GlGraphics, GlyphCache, Texture, TextureSettings};

#[derive(Clone, Hash, PartialEq, Eq)]
enum Music {
    Name(Arc<String>),
}

#[derive(Clone, Hash, PartialEq, Eq)]
enum Sound {
    Name(Arc<String>),
}

fn main() {
    let file = std::env::args_os().nth(1)
        .and_then(|s| s.into_string().ok());
    let file = if let Some(file) = file {
        use std::env::set_current_dir;
        use std::path::PathBuf;

        let path: PathBuf = (&file).into();
        if let Some(parent) = path.parent() {
            if let Err(_) = set_current_dir(parent) {
                file
            } else {
                path.file_name().unwrap().to_str().unwrap().to_owned()
            }
        } else {
            file
        }
    } else {
        println!("dyongame <file.dyon>");
        return;
    };

    let opengl = OpenGL::V3_2;
    let mut window: Sdl2Window =
        WindowSettings::new("dyongame", [512; 2])
        .exit_on_esc(true)
        .samples(4)
        .graphics_api(opengl)
        .build()
        .unwrap();
    let dyon_module = match load_module(&file) {
        None => return,
        Some(m) => Arc::new(m)
    };

    let mut factory = ();
    let mut dyon_runtime = Runtime::new();
    let fira_sans = include_bytes!("../assets/FiraSans-Regular.ttf");
    let hack = include_bytes!("../assets/Hack-Regular.ttf");
    let font_texture_settings = TextureSettings::new().filter(Filter::Nearest);
    let mut glyphs = vec![
        GlyphCache::from_bytes(&fira_sans[..], (), font_texture_settings.clone()).unwrap(),
        GlyphCache::from_bytes(&hack[..], (), font_texture_settings).unwrap()
    ];
    let mut font_names = FontNames(vec![
        Arc::new("FiraSans-Regular".to_owned()),
        Arc::new("Hack-Regular".to_owned()),
    ]);
    let mut images = vec![];
    let mut image_names = ImageNames(vec![]);
    let mut textures = vec![];
    let mut gl = GlGraphics::new(opengl);
    let mut events = Events::new(EventSettings::new());

    let mut e: Option<Event> = None;
    let sdl = window.sdl_context.clone();
    let factory_guard: CurrentGuard<()> = CurrentGuard::new(&mut factory);
    let window_guard = CurrentGuard::new(&mut window);
    let event_guard: CurrentGuard<Option<Event>> = CurrentGuard::new(&mut e);
    let glyphs_guard: CurrentGuard<Vec<GlyphCache>> = CurrentGuard::new(&mut glyphs);
    let font_names_guard: CurrentGuard<FontNames> = CurrentGuard::new(&mut font_names);
    let images_guard: CurrentGuard<Vec<RgbaImage>> = CurrentGuard::new(&mut images);
    let image_names_guard: CurrentGuard<ImageNames> = CurrentGuard::new(&mut image_names);
    let textures_guard: CurrentGuard<Vec<Texture>> = CurrentGuard::new(&mut textures);
    let gl_guard: CurrentGuard<GlGraphics> = CurrentGuard::new(&mut gl);
    let events_guard: CurrentGuard<Events> = CurrentGuard::new(&mut events);

    music::start_context::<Music, Sound, _>(&sdl, 16, || {
        if error(dyon_runtime.run(&dyon_module)) {
            return;
        }
    });

    drop(events_guard);
    drop(gl_guard);
    drop(textures_guard);
    drop(image_names_guard);
    drop(images_guard);
    drop(font_names_guard);
    drop(glyphs_guard);
    drop(event_guard);
    drop(window_guard);
    drop(factory_guard);
}

fn load_module(file: &str) -> Option<Module> {
    use dyon_functions::*;
    use dyon_interactive::add_functions;

    let mut module = Module::new();
    add_functions::<Sdl2Window, (), GlyphCache>(&mut module);
    module.add(Arc::new("render_source".into()), render_source, Dfn::nl(vec![], Type::Str));
    module.add(Arc::new("draw".into()), draw, Dfn::nl(vec![Type::array()], Type::Void));
    module.add(Arc::new("next_event".into()),
        next_event, Dfn::nl(vec![], Type::Bool));
    module.add(Arc::new("bind_sound__name_file".into()),
        bind_sound__name_file, Dfn::nl(vec![Type::Str; 2], Type::Void));
    module.add(Arc::new("bind_music__name_file".into()),
        bind_music__name_file, Dfn::nl(vec![Type::Str; 2], Type::Void));
    module.add(Arc::new("play_sound__name_repeat_volume".into()),
        play_sound__name_repeat_volume, Dfn::nl(vec![Type::Str, Type::F64, Type::F64], Type::Void));
    module.add(Arc::new("play_sound_forever__name_volume".into()),
        play_sound_forever__name_volume, Dfn::nl(vec![Type::Str, Type::F64], Type::Void));
    module.add(Arc::new("play_music__name_repeat".into()),
        play_music__name_repeat, Dfn::nl(vec![Type::Str, Type::F64], Type::Void));
    module.add(Arc::new("play_music_forever__name".into()),
        play_music_forever__name, Dfn::nl(vec![Type::Str], Type::Void));
    module.add(Arc::new("set_music_volume".into()),
        set_music_volume, Dfn::nl(vec![Type::F64], Type::Void));
    module.add(Arc::new("create_texture".into()),
        create_texture, Dfn::nl(vec![Type::F64], Type::F64));
    module.add(Arc::new("update__texture_image".into()),
        update__texture_image, Dfn::nl(vec![Type::F64, Type::F64], Type::Void)
    );

    if error(dyon::load_str(
        "render.dyon",
        Arc::new(include_str!("../src/render.dyon").into()),
        &mut module
    )) {
        return None;
    }

    if error(load(file, &mut module)) {
        None
    } else {
        Some(module)
    }
}

mod dyon_functions {
    use sdl2_window::Sdl2Window;
    use piston::input::{Event, RenderEvent};
    use piston::event_loop::Events;
    use opengl_graphics::{GlGraphics, GlyphCache, Texture, TextureSettings};
    use dyon::{Runtime, Variable};
    use dyon_interactive::{draw_2d, NO_EVENT};
    use current::Current;
    use std::sync::Arc;
    use music;
    use {Music, Sound};
    use image::RgbaImage;

    dyon_fn!{fn render_source() -> String {include_str!("../src/render.dyon").into()}}

    pub fn draw(rt: &mut Runtime) -> Result<(), String> {
        let e = unsafe { &*Current::<Option<Event>>::new() };
        let gl = unsafe { &mut *Current::<GlGraphics>::new() };
        let glyphs = unsafe { &mut *Current::<Vec<GlyphCache>>::new() };
        let textures = unsafe { &mut *Current::<Vec<Texture>>::new() };
        if let &Some(ref e) = e {
            if let Some(args) = e.render_args() {
                gl.draw(args.viewport(), |c, g| {
                    draw_2d(rt, glyphs, textures, c, g)
                })
            } else {
                Ok(())
            }
        } else {
            Err(NO_EVENT.into())
        }
    }

    pub fn create_texture(rt: &mut Runtime) -> Result<Variable, String> {
        use dyon::embed::PushVariable;

        let images = unsafe { &*Current::<Vec<RgbaImage>>::new() };
        let textures = unsafe { &mut *Current::<Vec<Texture>>::new() };
        let id: usize = rt.pop()?;
        let new_id = textures.len();
        let image = if let Some(x) = images.get(id) {
            x
        } else {
            return Err("Image id is out of bounds".into());
        };
        textures.push(Texture::from_image(image, &TextureSettings::new()));
        Ok(new_id.push_var())
    }

    #[allow(non_snake_case)]
    pub fn update__texture_image(rt: &mut Runtime) -> Result<(), String> {
        let images = unsafe { &*Current::<Vec<RgbaImage>>::new() };
        let textures = unsafe { &mut *Current::<Vec<Texture>>::new() };
        let image_id: usize = rt.pop()?;
        let texture_id: usize = rt.pop()?;
        let image = if let Some(x) = images.get(image_id) {
            x
        } else {
            return Err("Image id is out of bounds".into());
        };
        let texture = if let Some(x) = textures.get_mut(texture_id) {
            x
        } else {
            return Err("Texture id is out of bounds".into());
        };
        texture.update(&image);
        Ok(())
    }

    dyon_fn!{fn next_event() -> bool {
        let window = unsafe { &mut *Current::<Sdl2Window>::new() };
        let events = unsafe { &mut *Current::<Events>::new() };
        let e = unsafe { &mut *Current::<Option<Event>>::new() };
        if let Some(new_e) = events.next(window) {
            *e = Some(new_e);
            true
        } else {
            *e = None;
            false
        }
    }}

    dyon_fn!{fn bind_sound__name_file(name: Arc<String>, file: Arc<String>) {
        music::bind_sound_file(Sound::Name(name), &**file);
    }}

    dyon_fn!{fn bind_music__name_file(name: Arc<String>, file: Arc<String>) {
        music::bind_music_file(Music::Name(name), &**file);
    }}

    dyon_fn!{fn play_sound__name_repeat_volume(name: Arc<String>, repeat: f64, volume: f64) {
        use music::Repeat;

        let repeat = if repeat == -1.0 {Repeat::Forever} else {Repeat::Times(repeat as u16)};
        music::play_sound(&Sound::Name(name), repeat, volume);
    }}

    dyon_fn!{fn play_sound_forever__name_volume(name: Arc<String>, volume: f64) {
        use music::Repeat;

        music::play_sound(&Sound::Name(name), Repeat::Forever, volume);
    }}

    dyon_fn!{fn play_music__name_repeat(name: Arc<String>, repeat: f64) {
        use music::Repeat;

        let repeat = if repeat == -1.0 {Repeat::Forever} else {Repeat::Times(repeat as u16)};
        music::play_music(&Music::Name(name), repeat);
    }}

    dyon_fn!{fn play_music_forever__name(name: Arc<String>) {
        use music::Repeat;

        music::play_music(&Music::Name(name), Repeat::Forever);
    }}

    dyon_fn!{fn set_music_volume(volume: f64) {
        music::set_volume(volume);
    }}
}