~saiko/game

38e31508d85eac7e6a260928fea57087e5409f05 — 2xsaiko 6 months ago 87afca4
Reformat code
M src/cam.rs => src/cam.rs +58 -58
@@ 4,79 4,79 @@ use crate::math::*;
use crate::util::*;

pub struct Camera {
  pos: Vec3,
  yaw: f32,
  pitch: f32,
  roll: f32,
  view: Cell<Option<Mat4>>,
    pos: Vec3,
    yaw: f32,
    pitch: f32,
    roll: f32,
    view: Cell<Option<Mat4>>,
}

impl Camera {
  pub fn new() -> Self {
    Camera {
      pos: Vec3::of(0.0, 0.0, 0.0),
      yaw: 0.0,
      pitch: 0.0,
      roll: 0.0,
      view: Default::default(),
    pub fn new() -> Self {
        Camera {
            pos: Vec3::of(0.0, 0.0, 0.0),
            yaw: 0.0,
            pitch: 0.0,
            roll: 0.0,
            view: Default::default(),
        }
    }
  }

  pub fn pos(&self) -> Vec3 { self.pos }
    pub fn pos(&self) -> Vec3 { self.pos }

  pub fn yaw(&self) -> f32 { self.yaw }
    pub fn yaw(&self) -> f32 { self.yaw }

  pub fn pitch(&self) -> f32 { self.pitch }
    pub fn pitch(&self) -> f32 { self.pitch }

  pub fn roll(&self) -> f32 { self.roll }
    pub fn roll(&self) -> f32 { self.roll }

  pub fn set_pos(&mut self, pos: Vec3) {
    self.pos = pos;
    self.view.take();
  }
    pub fn set_pos(&mut self, pos: Vec3) {
        self.pos = pos;
        self.view.take();
    }

  pub fn shift(&mut self, offset: Vec3) {
    self.set_pos(self.pos() + offset);
  }
    pub fn shift(&mut self, offset: Vec3) {
        self.set_pos(self.pos() + offset);
    }

  pub fn set_yaw(&mut self, yaw: f32) {
    self.yaw = yaw.wheel(0.0, 360.0);
    self.view.take();
  }
    pub fn set_yaw(&mut self, yaw: f32) {
        self.yaw = yaw.wheel(0.0, 360.0);
        self.view.take();
    }

  pub fn set_pitch(&mut self, pitch: f32) {
    self.pitch = pitch.clamp(-95.0, 95.0);
    self.view.take();
  }
    pub fn set_pitch(&mut self, pitch: f32) {
        self.pitch = pitch.clamp(-95.0, 95.0);
        self.view.take();
    }

  pub fn set_roll(&mut self, roll: f32) {
    self.roll = roll.wheel(-180.0, 180.0);
    self.view.take();
  }
    pub fn set_roll(&mut self, roll: f32) {
        self.roll = roll.wheel(-180.0, 180.0);
        self.view.take();
    }

  pub fn set_rotation(&mut self, yaw: f32, pitch: f32, roll: f32) {
    self.set_yaw(yaw);
    self.set_pitch(pitch);
    self.set_roll(roll);
  }
    pub fn set_rotation(&mut self, yaw: f32, pitch: f32, roll: f32) {
        self.set_yaw(yaw);
        self.set_pitch(pitch);
        self.set_roll(roll);
    }

  pub fn rotate(&mut self, yaw: f32, pitch: f32, roll: f32) {
    self.set_rotation(self.yaw + yaw, self.pitch + pitch, self.roll + roll);
  }
    pub fn rotate(&mut self, yaw: f32, pitch: f32, roll: f32) {
        self.set_rotation(self.yaw + yaw, self.pitch + pitch, self.roll + roll);
    }

  pub fn get_matrix(&self) -> Mat4 {
    match self.view.get() {
      None => {
        let mat = Mat4::identity()
          .rotate(vec3(1.0, 0.0, 0.0), -90.0)
          .rotate(vec3(1.0, 0.0, 0.0), self.pitch)
          .rotate(vec3(0.0, 0.0, 1.0), self.yaw)
          .rotate(vec3(0.0, 1.0, 0.0), self.roll)
          .translate(self.pos);
        self.view.set(Some(mat));
        mat
      }
      Some(mat) => mat
    pub fn get_matrix(&self) -> Mat4 {
        match self.view.get() {
            None => {
                let mat = Mat4::identity()
                    .rotate(vec3(1.0, 0.0, 0.0), -90.0)
                    .rotate(vec3(1.0, 0.0, 0.0), self.pitch)
                    .rotate(vec3(0.0, 0.0, 1.0), self.yaw)
                    .rotate(vec3(0.0, 1.0, 0.0), self.roll)
                    .translate(self.pos);
                self.view.set(Some(mat));
                mat
            }
            Some(mat) => mat
        }
    }
  }
}
\ No newline at end of file

M src/cmd/base.rs => src/cmd/base.rs +169 -167
@@ 8,229 8,231 @@ use std::sync::{Arc, Mutex};
/// A command scheduler. This may be freely shared across threads to schedule commands from anywhere.
#[derive(Default, Clone)]
pub struct CommandScheduler {
  scheduled: Arc<Mutex<Vec<ExecutionState>>>,
    scheduled: Arc<Mutex<Vec<ExecutionState>>>,
}

impl CommandScheduler {
  /// Parse and schedule the given script source code for execution.
  pub fn exec(&self, script: &str, source: ExecSource) {
    let lines = tokenize(script);
    self.scheduled.lock().unwrap().push(ExecutionState::new(lines, source));
  }

  /// Parse and schedule the given script file for execution.
  pub fn exec_path(&self, script: impl AsRef<Path>, source: ExecSource) -> Result<(), io::Error> {
    read_to_string(&script).map(|s| self.exec(&s, source))
  }
    /// Parse and schedule the given script source code for execution.
    pub fn exec(&self, script: &str, source: ExecSource) {
        let lines = tokenize(script);
        self.scheduled.lock().unwrap().push(ExecutionState::new(lines, source));
    }

    /// Parse and schedule the given script file for execution.
    pub fn exec_path(&self, script: impl AsRef<Path>, source: ExecSource) -> Result<(), io::Error> {
        read_to_string(&script).map(|s| self.exec(&s, source))
    }
}

/// The command dispatcher used for actually executing commands.
pub struct CommandDispatcher<U: CommandExecutor> {
  executor: U,
  scheduler: CommandScheduler,
    executor: U,
    scheduler: CommandScheduler,
}

impl<U: CommandExecutor> CommandDispatcher<U> {
  pub fn new(executor: U) -> Self {
    CommandDispatcher {
      executor,
      scheduler: CommandScheduler::default(),
    pub fn new(executor: U) -> Self {
        CommandDispatcher {
            executor,
            scheduler: CommandScheduler::default(),
        }
    }
  }

  pub fn scheduler(&self) -> &CommandScheduler { &self.scheduler }

  /// Execute one step of the script (until completion or [`ExecState::Suspend`] is reached)
  ///
  /// [`ExecState::Suspend`]: enum.ExecState.html#variant.Suspend
  fn step(executor: &mut U, script: &mut ExecutionState) {
    let source = script.source;
    while !script.is_done() {
      let next = script.next().unwrap();
      if let Some(command) = next.first() {
        let args = &next[1..];
        let next_state = executor.exec(command, &args.iter().map(|s| s.as_str()).collect::<Vec<_>>(), source);
        match next_state {
          ExecState::Continue => {}
          ExecState::EnterSubroutine(source) => {
            script.enter_subroutine(tokenize(&source));
          }
          ExecState::Suspend => {
            break;
          }
          ExecState::Abort => {
            script.ret();
            break;
          }

    pub fn scheduler(&self) -> &CommandScheduler { &self.scheduler }

    /// Execute one step of the script (until completion or [`ExecState::Suspend`] is reached)
    ///
    /// [`ExecState::Suspend`]: enum.ExecState.html#variant.Suspend
    fn step(executor: &mut U, script: &mut ExecutionState) {
        let source = script.source;
        while !script.is_done() {
            let next = script.next().unwrap();
            if let Some(command) = next.first() {
                let args = &next[1..];
                let next_state = executor.exec(command, &args.iter().map(|s| s.as_str()).collect::<Vec<_>>(), source);
                match next_state {
                    ExecState::Continue => {}
                    ExecState::EnterSubroutine(source) => {
                        script.enter_subroutine(tokenize(&source));
                    }
                    ExecState::Suspend => {
                        break;
                    }
                    ExecState::Abort => {
                        script.ret();
                        break;
                    }
                }
            }
        }
      }
    }
  }

  /// Execute all pending scripts for one step (until a command returns [`ExecState::Suspend`])
  ///
  /// [`ExecState::Suspend`]: enum.ExecState.html#variant.Suspend
  pub fn resume(&mut self) {
    let mut suspended = replace(&mut *self.scheduler.scheduled.lock().unwrap(), vec![]);
    for entry in suspended.iter_mut() {
      Self::step(&mut self.executor, entry);

    /// Execute all pending scripts for one step (until a command returns [`ExecState::Suspend`])
    ///
    /// [`ExecState::Suspend`]: enum.ExecState.html#variant.Suspend
    pub fn resume(&mut self) {
        let mut suspended = replace(&mut *self.scheduler.scheduled.lock().unwrap(), vec![]);
        for entry in suspended.iter_mut() {
            Self::step(&mut self.executor, entry);
        }
        let mut s1 = self.scheduler.scheduled.lock().unwrap();
        swap(&mut *s1, &mut suspended);
        suspended.into_iter()
            .filter(|el| !el.is_done())
            .for_each(|el| s1.push(el));
    }
    let mut s1 = self.scheduler.scheduled.lock().unwrap();
    swap(&mut *s1, &mut suspended);
    suspended.into_iter()
      .filter(|el| !el.is_done())
      .for_each(|el| s1.push(el));
  }

  /// Execute all pending scripts until completion.
  /// This will respect a command returning [`ExecState::Suspend`] by yielding execution for other scripts,
  /// but will eventually continue executing that script until completion.
  ///
  /// [`ExecState::Suspend`]: enum.ExecState.html#variant.Suspend
  pub fn resume_until_empty(&mut self) {
    let mut suspended = replace(&mut *self.scheduler.scheduled.lock().unwrap(), vec![]);
    while !suspended.is_empty() {
      for entry in suspended.iter_mut() {
        Self::step(&mut self.executor, entry);
      }
      suspended.retain(|el| !el.is_done());
      self.scheduler.scheduled.lock().unwrap().drain(..).for_each(|el| suspended.push(el));

    /// Execute all pending scripts until completion.
    /// This will respect a command returning [`ExecState::Suspend`] by yielding execution for other scripts,
    /// but will eventually continue executing that script until completion.
    ///
    /// [`ExecState::Suspend`]: enum.ExecState.html#variant.Suspend
    pub fn resume_until_empty(&mut self) {
        let mut suspended = replace(&mut *self.scheduler.scheduled.lock().unwrap(), vec![]);
        while !suspended.is_empty() {
            for entry in suspended.iter_mut() {
                Self::step(&mut self.executor, entry);
            }
            suspended.retain(|el| !el.is_done());
            self.scheduler.scheduled.lock().unwrap().drain(..).for_each(|el| suspended.push(el));
        }
        let mut s1 = self.scheduler.scheduled.lock().unwrap();
        swap(&mut *s1, &mut suspended);
        suspended.into_iter()
            .filter(|el| !el.is_done())
            .for_each(|el| s1.push(el));
    }
    let mut s1 = self.scheduler.scheduled.lock().unwrap();
    swap(&mut *s1, &mut suspended);
    suspended.into_iter()
      .filter(|el| !el.is_done())
      .for_each(|el| s1.push(el));
  }
}

pub trait CommandExecutor {
  fn exec(&mut self, command: &str, args: &[&str], source: ExecSource) -> ExecState;
    fn exec(&mut self, command: &str, args: &[&str], source: ExecSource) -> ExecState;
}

pub struct SimpleExecutor<T: FnMut(&str, &[&str])> {
  op: T,
    op: T,
}

impl<T: FnMut(&str, &[&str])> SimpleExecutor<T> {
  pub fn new(op: T) -> Self {
    SimpleExecutor { op }
  }
    pub fn new(op: T) -> Self {
        SimpleExecutor { op }
    }
}

impl<T: FnMut(&str, &[&str])> CommandExecutor for SimpleExecutor<T> {
  fn exec(&mut self, command: &str, args: &[&str], _source: ExecSource) -> ExecState {
    (self.op)(command, args);
    ExecState::Continue
  }
    fn exec(&mut self, command: &str, args: &[&str], _source: ExecSource) -> ExecState {
        (self.op)(command, args);
        ExecState::Continue
    }
}

pub struct ExecutionState {
  source: ExecSource,
  stack: Vec<SubroutineState>,
    source: ExecSource,
    stack: Vec<SubroutineState>,
}

impl ExecutionState {
  pub fn new(script: Vec<Vec<String>>, source: ExecSource) -> Self {
    ExecutionState {
      source,
      stack: vec![SubroutineState { line: 0, script }],
    pub fn new(script: Vec<Vec<String>>, source: ExecSource) -> Self {
        ExecutionState {
            source,
            stack: vec![SubroutineState { line: 0, script }],
        }
    }

    pub fn enter_subroutine(&mut self, script: Vec<Vec<String>>) {
        self.stack.push(SubroutineState { line: 0, script })
    }
  }

  pub fn enter_subroutine(&mut self, script: Vec<Vec<String>>) {
    self.stack.push(SubroutineState { line: 0, script })
  }

  pub fn next(&mut self) -> Option<Cow<[String]>> {
    let last_insn = {
      let tos = self.stack.last_mut()?;
      tos.line + 1 == tos.script.len()
    };
    if last_insn {
      let mut tos = self.stack.pop().unwrap();
      Some(Cow::Owned(tos.script.pop().unwrap()))
    } else {
      let tos = self.stack.last_mut().unwrap();
      let r = &tos.script[tos.line];
      tos.line += 1;
      Some(Cow::Borrowed(r))

    pub fn next(&mut self) -> Option<Cow<[String]>> {
        let last_insn = {
            let tos = self.stack.last_mut()?;
            tos.line + 1 == tos.script.len()
        };
        if last_insn {
            let mut tos = self.stack.pop().unwrap();
            Some(Cow::Owned(tos.script.pop().unwrap()))
        } else {
            let tos = self.stack.last_mut().unwrap();
            let r = &tos.script[tos.line];
            tos.line += 1;
            Some(Cow::Borrowed(r))
        }
    }
  }

  pub fn is_done(&self) -> bool { !self.stack.iter().any(|el| !el.script.is_empty()) }
    pub fn is_done(&self) -> bool { !self.stack.iter().any(|el| !el.script.is_empty()) }

  pub fn ret(&mut self) { self.stack.pop(); }
    pub fn ret(&mut self) { self.stack.pop(); }
}

pub struct SubroutineState {
  line: usize,
  script: Vec<Vec<String>>,
    line: usize,
    script: Vec<Vec<String>>,
}

#[derive(Debug, Clone, Copy)]
pub enum ExecSource {
  Console,
  Key,
  Event,
  Other,
    Console,
    Key,
    Event,
    Other,
}

pub enum ExecState {
  Continue,
  EnterSubroutine(String),
  Suspend,
  Abort,
    Continue,
    EnterSubroutine(String),
    Suspend,
    Abort,
}

/// Tokenize script source, removing comments (starting with `//`).
/// Returns a list of command executions (command + arguments)
fn tokenize(s: &str) -> Vec<Vec<String>> {
  let mut esc = false;
  let mut quoted = false;
  let mut commands = vec![];
  let mut current = vec![];
  let mut sb = String::new();

  fn next_token(sb: &mut String, current: &mut Vec<String>) {
    if !sb.trim().is_empty() {
      current.push(sb.clone());
    let mut esc = false;
    let mut quoted = false;
    let mut commands = vec![];
    let mut current = vec![];
    let mut sb = String::new();

    fn next_token(sb: &mut String, current: &mut Vec<String>) {
        if !sb.trim().is_empty() {
            current.push(sb.clone());
        }
        sb.clear();
    }
    sb.clear();
  };
    ;

  fn next_command(sb: &mut String, current: &mut Vec<String>, commands: &mut Vec<Vec<String>>) {
    next_token(sb, current);
    if !current.is_empty() {
      commands.push(current.clone());
    fn next_command(sb: &mut String, current: &mut Vec<String>, commands: &mut Vec<Vec<String>>) {
        next_token(sb, current);
        if !current.is_empty() {
            commands.push(current.clone());
        }
        current.clear();
    }
    current.clear();
  };

  for line in s.lines() {
    let get = |i| line.chars().skip(i).next();

    for (pos, c) in line.chars().enumerate() {
      if esc {
        sb.push(c);
        esc = false;
      } else if !quoted && c == '/' && get(pos + 1) == Some('/') {
        break;
      } else if !quoted && c == ';' {
    ;

    for line in s.lines() {
        let get = |i| line.chars().skip(i).next();

        for (pos, c) in line.chars().enumerate() {
            if esc {
                sb.push(c);
                esc = false;
            } else if !quoted && c == '/' && get(pos + 1) == Some('/') {
                break;
            } else if !quoted && c == ';' {
                next_command(&mut sb, &mut current, &mut commands);
            } else if !quoted && c == ' ' {
                next_token(&mut sb, &mut current);
            } else if c == '"' {
                quoted = !quoted;
            } else if c == '\\' {
                esc = true;
            } else {
                sb.push(c);
            }
        }

        next_command(&mut sb, &mut current, &mut commands);
      } else if !quoted && c == ' ' {
        next_token(&mut sb, &mut current);
      } else if c == '"' {
        quoted = !quoted;
      } else if c == '\\' {
        esc = true;
      } else {
        sb.push(c);
      }
    }

    next_command(&mut sb, &mut current, &mut commands);
  }

  commands
    commands
}

M src/cmd/builtin.rs => src/cmd/builtin.rs +53 -53
@@ 6,62 6,62 @@ use itertools::Itertools;
use crate::cmd::{command, EngineBuilder};

pub fn register_commands<T: Write>(builder: EngineBuilder<T>) -> EngineBuilder<T> {
  builder
    .with_command("list", box command(|_args, ctx, output| {
      let builtins = ["alias", "unalias", "wait"];
    builder
        .with_command("list", box command(|_args, ctx, output| {
            let builtins = ["alias", "unalias", "wait"];

      enum EntryType {
        Builtin,
        Command,
        Alias,
        ConVar,
      }
            enum EntryType {
                Builtin,
                Command,
                Alias,
                ConVar,
            }

      struct HelpEntry<'a> {
        name: &'a str,
        desc: Option<&'a str>,
        entry_type: EntryType,
      }
            struct HelpEntry<'a> {
                name: &'a str,
                desc: Option<&'a str>,
                entry_type: EntryType,
            }

      let mut map = HashMap::new();
            let mut map = HashMap::new();

      builtins.iter()
        .map(|name| HelpEntry {
          name,
          desc: ctx.registry.help_strs.get(*name).map(|s| &s[..]),
          entry_type: EntryType::Builtin,
        })
        .chain(
          ctx.registry.commands.iter()
            .map(|(name, _)| HelpEntry {
              name,
              desc: ctx.registry.help_strs.get(name).map(|s| &s[..]),
              entry_type: EntryType::Command,
            }))
        .chain(ctx.registry.cvars.iter()
          .map(|(name, _)| HelpEntry {
            name,
            desc: ctx.registry.help_strs.get(name).map(|s| &s[..]),
            entry_type: EntryType::ConVar,
          }))
        .chain(ctx.state.aliases.iter()
          .map(|(name, _)| HelpEntry {
            name,
            desc: None,
            entry_type: EntryType::Alias,
          }))
        .for_each(|el| { map.insert(el.name, el); });
            builtins.iter()
                .map(|name| HelpEntry {
                    name,
                    desc: ctx.registry.help_strs.get(*name).map(|s| &s[..]),
                    entry_type: EntryType::Builtin,
                })
                .chain(
                    ctx.registry.commands.iter()
                        .map(|(name, _)| HelpEntry {
                            name,
                            desc: ctx.registry.help_strs.get(name).map(|s| &s[..]),
                            entry_type: EntryType::Command,
                        }))
                .chain(ctx.registry.cvars.iter()
                    .map(|(name, _)| HelpEntry {
                        name,
                        desc: ctx.registry.help_strs.get(name).map(|s| &s[..]),
                        entry_type: EntryType::ConVar,
                    }))
                .chain(ctx.state.aliases.iter()
                    .map(|(name, _)| HelpEntry {
                        name,
                        desc: None,
                        entry_type: EntryType::Alias,
                    }))
                .for_each(|el| { map.insert(el.name, el); });

      map.into_iter().sorted_by_key(|(a, _)| *a).for_each(|(_, el)| {
        writeln!(output, "{} ({}) - {}", el.name, match el.entry_type {
          EntryType::Command => "command",
          EntryType::Alias => "alias",
          EntryType::ConVar => "cvar",
          EntryType::Builtin => "built-in",
        }, el.desc.unwrap_or("")).unwrap();
      });
    }))
    .with_help_text("list", "list available commands")
    .with_command("echo", box command(|args, _, output| writeln!(output, "{}", args.join(" ")).unwrap()))
    .with_help_text("echo", "print text to the console")
            map.into_iter().sorted_by_key(|(a, _)| *a).for_each(|(_, el)| {
                writeln!(output, "{} ({}) - {}", el.name, match el.entry_type {
                    EntryType::Command => "command",
                    EntryType::Alias => "alias",
                    EntryType::ConVar => "cvar",
                    EntryType::Builtin => "built-in",
                }, el.desc.unwrap_or("")).unwrap();
            });
        }))
        .with_help_text("list", "list available commands")
        .with_command("echo", box command(|args, _, output| writeln!(output, "{}", args.join(" ")).unwrap()))
        .with_help_text("echo", "print text to the console")
}
\ No newline at end of file

M src/cmd/mod.rs => src/cmd/mod.rs +237 -237
@@ 11,325 11,325 @@ mod base;
mod builtin;

pub struct EngineBuilder<T: Write> {
  output: T,
  registry: Registry,
    output: T,
    registry: Registry,
}

impl<T: Write> EngineBuilder<T> {
  pub fn new(output: T) -> Self {
    EngineBuilder {
      output,
      registry: Registry::default(),
    pub fn new(output: T) -> Self {
        EngineBuilder {
            output,
            registry: Registry::default(),
        }
    }
  }

  pub fn with_default(self) -> Self {
    builtin::register_commands(self)
  }
    pub fn with_default(self) -> Self {
        builtin::register_commands(self)
    }

  pub fn with_command(mut self, name: &str, command: Box<dyn Command>) -> Self {
    if let Some(_) = self.registry.commands.insert(name.to_owned(), command) {
      panic!("Command '{}' already registered!", name);
    pub fn with_command(mut self, name: &str, command: Box<dyn Command>) -> Self {
        if let Some(_) = self.registry.commands.insert(name.to_owned(), command) {
            panic!("Command '{}' already registered!", name);
        }
        self
    }
    self
  }

  pub fn with_cvar(mut self, name: &str, cvar: ConVar) -> Self {
    if let Some(_) = self.registry.cvars.insert(name.to_owned(), cvar) {
      panic!("ConVar '{}' already registered!", name);
    pub fn with_cvar(mut self, name: &str, cvar: ConVar) -> Self {
        if let Some(_) = self.registry.cvars.insert(name.to_owned(), cvar) {
            panic!("ConVar '{}' already registered!", name);
        }
        self
    }
    self
  }

  pub fn with_help_text(mut self, name: &str, text: &str) -> Self {
    if let Some(_) = self.registry.help_strs.insert(name.to_owned(), text.to_owned()) {
      panic!("Help text for '{}' already registered!", name);
    pub fn with_help_text(mut self, name: &str, text: &str) -> Self {
        if let Some(_) = self.registry.help_strs.insert(name.to_owned(), text.to_owned()) {
            panic!("Help text for '{}' already registered!", name);
        }
        self
    }
    self
  }

  pub fn build(self) -> ConsoleEngine<T> {
    ConsoleEngine::new(self.registry, self.output)
  }
    pub fn build(self) -> ConsoleEngine<T> {
        ConsoleEngine::new(self.registry, self.output)
    }
}

#[derive(Default)]
pub struct Registry {
  commands: HashMap<String, Box<dyn Command>>,
  cvars: HashMap<String, ConVar>,
  generators: Vec<Box<dyn Persistable>>,
  help_strs: HashMap<String, String>,
    commands: HashMap<String, Box<dyn Command>>,
    cvars: HashMap<String, ConVar>,
    generators: Vec<Box<dyn Persistable>>,
    help_strs: HashMap<String, String>,
}

#[derive(Default)]
pub struct State {
  aliases: HashMap<String, String>,
    aliases: HashMap<String, String>,
}

pub struct Executor<T: Write> {
  registry: Rc<Registry>,
  state: RefCell<State>,
  output: T,
    registry: Rc<Registry>,
    state: RefCell<State>,
    output: T,
}

impl<T: Write> CommandExecutor for Executor<T> {
  fn exec(&mut self, command: &str, args: &[&str], _: ExecSource) -> ExecState {
    match command {
      "alias" => {
        match args {
          &[] => {}
          &[alias] => {
            match self.state.borrow().aliases.get(alias) {
              None => {
                writeln!(self.output, "'{}' is not an alias", alias).unwrap();
              }
              Some(script) => {
                writeln!(self.output, "'{}' = '{}'", alias, script).unwrap();
              }
    fn exec(&mut self, command: &str, args: &[&str], _: ExecSource) -> ExecState {
        match command {
            "alias" => {
                match args {
                    &[] => {}
                    &[alias] => {
                        match self.state.borrow().aliases.get(alias) {
                            None => {
                                writeln!(self.output, "'{}' is not an alias", alias).unwrap();
                            }
                            Some(script) => {
                                writeln!(self.output, "'{}' = '{}'", alias, script).unwrap();
                            }
                        }
                    }
                    &[alias, script, ..] => {
                        self.state.borrow_mut().aliases.insert(alias.to_owned(), script.to_owned());
                    }
                }
                ExecState::Continue
            }
            "unalias" => {
                match args {
                    &[alias] => {
                        self.state.borrow_mut().aliases.remove(alias);
                    }
                    _ => {}
                }
                ExecState::Continue
            }
            "wait" => {
                ExecState::Suspend
            }
            _ => {
                if let Some(script) = self.state.borrow().aliases.get(command) {
                    ExecState::EnterSubroutine(script.clone())
                } else if let Some(cvar) = self.registry.cvars.get(command) {
                    if args.len() == 0 {
                        writeln!(self.output, "{} = {}", command, cvar.fmt_values()).unwrap();
                    } else {
                        cvar.set_values(args);
                    }
                    ExecState::Continue
                } else if let Some(cmd) = self.registry.commands.get(command) {
                    let ec = ExecutionContext {
                        registry: &self.registry,
                        state: &*self.state.borrow(),
                    };
                    cmd.exec(args, &ec, &mut self.output)
                } else {
                    writeln!(self.output, "Command not found: {}", command).unwrap();
                    ExecState::Continue
                }
            }
          }
          &[alias, script, ..] => {
            self.state.borrow_mut().aliases.insert(alias.to_owned(), script.to_owned());
          }
        }
        ExecState::Continue
      }
      "unalias" => {
        match args {
          &[alias] => {
            self.state.borrow_mut().aliases.remove(alias);
          }
          _ => {}
        }
        ExecState::Continue
      }
      "wait" => {
        ExecState::Suspend
      }
      _ => {
        if let Some(script) = self.state.borrow().aliases.get(command) {
          ExecState::EnterSubroutine(script.clone())
        } else if let Some(cvar) = self.registry.cvars.get(command) {
          if args.len() == 0 {
            writeln!(self.output, "{} = {}", command, cvar.fmt_values()).unwrap();
          } else {
            cvar.set_values(args);
          }
          ExecState::Continue
        } else if let Some(cmd) = self.registry.commands.get(command) {
          let ec = ExecutionContext {
            registry: &self.registry,
            state: &*self.state.borrow(),
          };
          cmd.exec(args, &ec, &mut self.output)
        } else {
          writeln!(self.output, "Command not found: {}", command).unwrap();
          ExecState::Continue
        }
      }
    }
  }
}

pub struct ExecutionContext<'a> {
  pub registry: &'a Registry,
  pub state: &'a State,
    pub registry: &'a Registry,
    pub state: &'a State,
}

pub struct ConsoleEngine<T: Write> {
  registry: Rc<Registry>,
  dispatcher: CommandDispatcher<Executor<T>>,
    registry: Rc<Registry>,
    dispatcher: CommandDispatcher<Executor<T>>,
}

impl<T: Write> ConsoleEngine<T> {
  fn new(registry: Registry, output: T) -> Self {
    let reg = Rc::new(registry);
    ConsoleEngine {
      registry: reg.clone(),
      dispatcher: CommandDispatcher::new(Executor {
        registry: reg,
        state: RefCell::new(State::default()),
        output,
      }),
    fn new(registry: Registry, output: T) -> Self {
        let reg = Rc::new(registry);
        ConsoleEngine {
            registry: reg.clone(),
            dispatcher: CommandDispatcher::new(Executor {
                registry: reg,
                state: RefCell::new(State::default()),
                output,
            }),
        }
    }
  }

  pub fn get_cvar(&self, name: &str) -> Option<&ConVar> { self.registry.cvars.get(name) }
    pub fn get_cvar(&self, name: &str) -> Option<&ConVar> { self.registry.cvars.get(name) }

  pub fn scheduler(&self) -> &CommandScheduler { self.dispatcher.scheduler() }
    pub fn scheduler(&self) -> &CommandScheduler { self.dispatcher.scheduler() }

  pub fn dispatcher(&self) -> &CommandDispatcher<Executor<T>> { &self.dispatcher }
    pub fn dispatcher(&self) -> &CommandDispatcher<Executor<T>> { &self.dispatcher }

  pub fn dispatcher_mut(&mut self) -> &mut CommandDispatcher<Executor<T>> { &mut self.dispatcher }
    pub fn dispatcher_mut(&mut self) -> &mut CommandDispatcher<Executor<T>> { &mut self.dispatcher }
}

pub trait Command {
  fn exec(&self, args: &[&str], ctx: &ExecutionContext, output: &mut dyn Write) -> ExecState;
    fn exec(&self, args: &[&str], ctx: &ExecutionContext, output: &mut dyn Write) -> ExecState;
}

pub fn command(op: impl Fn(&[&str], &ExecutionContext, &mut dyn Write)) -> impl Command {
  struct Impl<T: Fn(&[&str], &ExecutionContext, &mut dyn Write)> {
    op: T,
  }

  impl<T: Fn(&[&str], &ExecutionContext, &mut dyn Write)> Command for Impl<T> {
    fn exec(&self, args: &[&str], ctx: &ExecutionContext, output: &mut dyn Write) -> ExecState {
      (self.op)(args, ctx, output);
      ExecState::Continue
    struct Impl<T: Fn(&[&str], &ExecutionContext, &mut dyn Write)> {
        op: T,
    }

    impl<T: Fn(&[&str], &ExecutionContext, &mut dyn Write)> Command for Impl<T> {
        fn exec(&self, args: &[&str], ctx: &ExecutionContext, output: &mut dyn Write) -> ExecState {
            (self.op)(args, ctx, output);
            ExecState::Continue
        }
    }
  }

  Impl { op }
    Impl { op }
}

pub fn expand_command(op: impl Fn(&[&str], &ExecutionContext, &mut dyn Write) -> Option<String>) -> impl Command {
  struct Impl<T: Fn(&[&str], &ExecutionContext, &mut dyn Write) -> Option<String>> {
    op: T,
  }

  impl<T: Fn(&[&str], &ExecutionContext, &mut dyn Write) -> Option<String>> Command for Impl<T> {
    fn exec(&self, args: &[&str], ctx: &ExecutionContext, output: &mut dyn Write) -> ExecState {
      match (self.op)(args, ctx, output) {
        None => ExecState::Continue,
        Some(s) => ExecState::EnterSubroutine(s),
      }
    struct Impl<T: Fn(&[&str], &ExecutionContext, &mut dyn Write) -> Option<String>> {
        op: T,
    }
  }

  Impl { op }
    impl<T: Fn(&[&str], &ExecutionContext, &mut dyn Write) -> Option<String>> Command for Impl<T> {
        fn exec(&self, args: &[&str], ctx: &ExecutionContext, output: &mut dyn Write) -> ExecState {
            match (self.op)(args, ctx, output) {
                None => ExecState::Continue,
                Some(s) => ExecState::EnterSubroutine(s),
            }
        }
    }

    Impl { op }
}

pub trait Persistable {
  fn file(&self) -> &str;
    fn file(&self) -> &str;

  fn write(&self, out: &mut dyn Write);
    fn write(&self, out: &mut dyn Write);
}

#[derive(Debug, Clone)]
pub enum ConVar {
  String {
    default: String,
    value: RefCell<String>,
  },
  Number {
    default: f64,
    value: Cell<f64>,
    min: Option<f64>,
    max: Option<f64>,
  },
  Integer {
    default: i64,
    value: Cell<i64>,
    min: Option<i64>,
    max: Option<i64>,
  },
    String {
        default: String,
        value: RefCell<String>,
    },
    Number {
        default: f64,
        value: Cell<f64>,
        min: Option<f64>,
        max: Option<f64>,
    },
    Integer {
        default: i64,
        value: Cell<i64>,
        min: Option<i64>,
        max: Option<i64>,
    },
}

impl ConVar {
  pub fn of_string(default: &str) -> Self {
    ConVar::String {
      default: default.to_owned(),
      value: RefCell::new(default.to_owned()),
    }
  }

  pub fn of_num(default: f64, min: Option<f64>, max: Option<f64>) -> Self {
    ConVar::Number {
      default,
      value: Cell::new(default),
      min,
      max,
    }
  }

  pub fn of_int(default: i64, min: Option<i64>, max: Option<i64>) -> Self {
    ConVar::Integer {
      default,
      value: Cell::new(default),
      min,
      max,
    pub fn of_string(default: &str) -> Self {
        ConVar::String {
            default: default.to_owned(),
            value: RefCell::new(default.to_owned()),
        }
    }
  }

  pub fn of_bool(default: bool) -> Self {
    ConVar::of_int(if default { 1 } else { 0 }, Some(0), Some(1))
  }

  pub fn fmt_values(&self) -> String {
    fn fmt_num<T: Display>(def: &T, value: &T, min: &Option<T>, max: &Option<T>) -> String {
      let mut s = format!("{} (default {}", value, def);
      if let Some(min) = min { s.push_str(&format!(", min {}", min)); }
      if let Some(max) = max { s.push_str(&format!(", max {}", max)); }
      s.push(')');
      s

    pub fn of_num(default: f64, min: Option<f64>, max: Option<f64>) -> Self {
        ConVar::Number {
            default,
            value: Cell::new(default),
            min,
            max,
        }
    }

    match self {
      ConVar::String { default, value } => format!("'{}' (default '{}')", value.borrow(), default),
      ConVar::Number { default, value, min, max } => fmt_num(default, &value.get(), min, max),
      ConVar::Integer { default, value, min, max } => fmt_num(default, &value.get(), min, max),
    pub fn of_int(default: i64, min: Option<i64>, max: Option<i64>) -> Self {
        ConVar::Integer {
            default,
            value: Cell::new(default),
            min,
            max,
        }
    }
  }

  pub fn set_values(&self, args: &[&str]) {
    fn parse_num<T: FromStr + Copy>(s: &str, value: &Cell<T>, min: Option<impl Fn(T) -> T>, max: Option<impl Fn(T) -> T>) {
      let val = s.parse::<T>();
      if let Ok(mut val) = val {
        if let Some(min) = min { val = min(val) }
        if let Some(max) = max { val = max(val) }
        value.set(val);
      }

    pub fn of_bool(default: bool) -> Self {
        ConVar::of_int(if default { 1 } else { 0 }, Some(0), Some(1))
    }

    match args.get(0) {
      Some(&s) => {
    pub fn fmt_values(&self) -> String {
        fn fmt_num<T: Display>(def: &T, value: &T, min: &Option<T>, max: &Option<T>) -> String {
            let mut s = format!("{} (default {}", value, def);
            if let Some(min) = min { s.push_str(&format!(", min {}", min)); }
            if let Some(max) = max { s.push_str(&format!(", max {}", max)); }
            s.push(')');
            s
        }

        match self {
          ConVar::String { value, .. } => {
            value.replace(s.to_owned());
          }
          ConVar::Number { value, min, max, .. } => {
            parse_num(s, value,
                      min.map(|min| move |v| min.max(v)),
                      max.map(|max| move |v| max.min(v)));
          }
          ConVar::Integer { value, min, max, .. } => {
            parse_num(s, value,
                      min.map(|min| move |v| min.max(v)),
                      max.map(|max| move |v| max.min(v)));
          }
            ConVar::String { default, value } => format!("'{}' (default '{}')", value.borrow(), default),
            ConVar::Number { default, value, min, max } => fmt_num(default, &value.get(), min, max),
            ConVar::Integer { default, value, min, max } => fmt_num(default, &value.get(), min, max),
        }
      }
      None => {}
    }
  }

  pub fn get_str_value<'a>(&'a self) -> String {
    match self {
      ConVar::String { value, .. } => value.borrow().to_owned(),
      ConVar::Number { value, .. } => format!("{}", value.get()),
      ConVar::Integer { value, .. } => format!("{}", value.get()),
    pub fn set_values(&self, args: &[&str]) {
        fn parse_num<T: FromStr + Copy>(s: &str, value: &Cell<T>, min: Option<impl Fn(T) -> T>, max: Option<impl Fn(T) -> T>) {
            let val = s.parse::<T>();
            if let Ok(mut val) = val {
                if let Some(min) = min { val = min(val) }
                if let Some(max) = max { val = max(val) }
                value.set(val);
            }
        }

        match args.get(0) {
            Some(&s) => {
                match self {
                    ConVar::String { value, .. } => {
                        value.replace(s.to_owned());
                    }
                    ConVar::Number { value, min, max, .. } => {
                        parse_num(s, value,
                                  min.map(|min| move |v| min.max(v)),
                                  max.map(|max| move |v| max.min(v)));
                    }
                    ConVar::Integer { value, min, max, .. } => {
                        parse_num(s, value,
                                  min.map(|min| move |v| min.max(v)),
                                  max.map(|max| move |v| max.min(v)));
                    }
                }
            }
            None => {}
        }
    }
  }

  pub fn get_int_value(&self) -> Option<i64> {
    match self {
      ConVar::Integer { value, .. } => Some(value.get()),
      _ => None
    pub fn get_str_value<'a>(&'a self) -> String {
        match self {
            ConVar::String { value, .. } => value.borrow().to_owned(),
            ConVar::Number { value, .. } => format!("{}", value.get()),
            ConVar::Integer { value, .. } => format!("{}", value.get()),
        }
    }

    pub fn get_int_value(&self) -> Option<i64> {
        match self {
            ConVar::Integer { value, .. } => Some(value.get()),
            _ => None
        }
    }
  }

  pub fn get_float_value(&self) -> Option<f64> {
    match self {
      ConVar::Number { value, .. } => Some(value.get()),
      _ => None
    pub fn get_float_value(&self) -> Option<f64> {
        match self {
            ConVar::Number { value, .. } => Some(value.get()),
            _ => None
        }
    }
  }

  pub fn get_bool_value(&self) -> bool {
    match self {
      ConVar::Number { value, .. } => value.get() != 0.0,
      ConVar::Integer { value, .. } => value.get() != 0,
      _ => false,
    pub fn get_bool_value(&self) -> bool {
        match self {
            ConVar::Number { value, .. } => value.get() != 0.0,
            ConVar::Integer { value, .. } => value.get() != 0,
            _ => false,
        }
    }
  }
}

M src/font/bdf/draw.rs => src/font/bdf/draw.rs +153 -153
@@ 15,193 15,193 @@ use crate::tex::atlas::{Sprite, TextureAtlas};
use crate::util::{AnySurface, Color};

pub struct BakedFont<'a> {
  facade: &'a dyn Facade,
  bdf: Bdf,
  atlas: RefCell<TextureAtlas>,
  chars: RefCell<HashMap<char, Rc<Sprite>>>,
  prog: Rc<Program>,
    facade: &'a dyn Facade,
    bdf: Bdf,
    atlas: RefCell<TextureAtlas>,
    chars: RefCell<HashMap<char, Rc<Sprite>>>,
    prog: Rc<Program>,
}

impl<'a> BakedFont<'a> {
  pub fn new(facade: &'a dyn Facade, bdf: Bdf, prog: Rc<Program>) -> Self {
    BakedFont {
      facade,
      bdf,
      atlas: RefCell::new(TextureAtlas::new(facade, 1024, 1024)),
      chars: Default::default(),
      prog,
    }
  }

  fn get_char_sprite(&self, c: char) -> Rc<Sprite> {
    let mut chars = self.chars.borrow_mut();
    match chars.get(&c) {
      Some(spr) => spr.clone(),
      None => {
        let glyph = self.bdf.glyph_or_default(c);
        let sprite = self.atlas.borrow_mut().alloc(glyph.width, glyph.height).expect("Failed to allocate sprite for character");
        let mut vec: Vec<u8> = Vec::new();
        for i in 0..glyph.width * glyph.height {
          let val = if glyph.bits.contains(i as usize) { 0xFF } else { 0x00 };
          vec.push(val);
          vec.push(val);
          vec.push(val);
          vec.push(val);
    pub fn new(facade: &'a dyn Facade, bdf: Bdf, prog: Rc<Program>) -> Self {
        BakedFont {
            facade,
            bdf,
            atlas: RefCell::new(TextureAtlas::new(facade, 1024, 1024)),
            chars: Default::default(),
            prog,
        }
        let data = RawImage2d::from_raw_rgba(vec, (sprite.width(), sprite.height()));
        sprite.write(Rect { left: 0, bottom: 0, width: sprite.width(), height: sprite.height() }, data);
        let sprite = Rc::new(sprite);
        chars.insert(c, sprite.clone());
        sprite
      }
    }
  }

  pub fn create_data(&self, strs: &[(&str, f32, f32, Color)]) -> BakedString<'a> {
    let mut texture: Option<Rc<Texture2d>> = None;
    let mut result = BakedString {
      facade: self.facade,
      prog: self.prog.clone(),
      data: vec![],
    };

    let char_count = strs.iter().fold(0, |acc, &(a, ..)| acc + a.len());
    if char_count == 0 {
      return result;

    fn get_char_sprite(&self, c: char) -> Rc<Sprite> {
        let mut chars = self.chars.borrow_mut();
        match chars.get(&c) {
            Some(spr) => spr.clone(),
            None => {
                let glyph = self.bdf.glyph_or_default(c);
                let sprite = self.atlas.borrow_mut().alloc(glyph.width, glyph.height).expect("Failed to allocate sprite for character");
                let mut vec: Vec<u8> = Vec::new();
                for i in 0..glyph.width * glyph.height {
                    let val = if glyph.bits.contains(i as usize) { 0xFF } else { 0x00 };
                    vec.push(val);
                    vec.push(val);
                    vec.push(val);
                    vec.push(val);
                }
                let data = RawImage2d::from_raw_rgba(vec, (sprite.width(), sprite.height()));
                sprite.write(Rect { left: 0, bottom: 0, width: sprite.width(), height: sprite.height() }, data);
                let sprite = Rc::new(sprite);
                chars.insert(c, sprite.clone());
                sprite
            }
        }
    }

    let mut buf = Vec::with_capacity(char_count);
    pub fn create_data(&self, strs: &[(&str, f32, f32, Color)]) -> BakedString<'a> {
        let mut texture: Option<Rc<Texture2d>> = None;
        let mut result = BakedString {
            facade: self.facade,
            prog: self.prog.clone(),
            data: vec![],
        };

        let char_count = strs.iter().fold(0, |acc, &(a, ..)| acc + a.len());
        if char_count == 0 {
            return result;
        }

    for &(s, mut x, mut y, color) in strs {
      for c in s.chars() {
        let glyph = self.bdf.glyph_or_default(c);
        let sprite = self.get_char_sprite(c);
        let mut buf = Vec::with_capacity(char_count);

        if texture.as_ref().map(|t| t.get_id() != sprite.texture().get_id()).unwrap_or(false) {
          result.insert(texture.unwrap(), buf);
          buf = vec![];
        }
        for &(s, mut x, mut y, color) in strs {
            for c in s.chars() {
                let glyph = self.bdf.glyph_or_default(c);
                let sprite = self.get_char_sprite(c);

        let x1 = x + glyph.width as f32;
        let y1 = y + glyph.height as f32;
                if texture.as_ref().map(|t| t.get_id() != sprite.texture().get_id()).unwrap_or(false) {
                    result.insert(texture.unwrap(), buf);
                    buf = vec![];
                }

        buf.push(Vertex {
          xyz: [x, y, 0.0],
          uv: [sprite.min_u(), sprite.min_v()],
          color: color.into_array(),
          size: [glyph.width as f32, glyph.height as f32],
          uv_size: [sprite.tex_width(), sprite.tex_height()],
        });
                let x1 = x + glyph.width as f32;
                let y1 = y + glyph.height as f32;

        texture = Some(sprite.texture().clone());
                buf.push(Vertex {
                    xyz: [x, y, 0.0],
                    uv: [sprite.min_u(), sprite.min_v()],
                    color: color.into_array(),
                    size: [glyph.width as f32, glyph.height as f32],
                    uv_size: [sprite.tex_width(), sprite.tex_height()],
                });

        x += glyph.dwidth_x as f32;
        y += glyph.dwidth_y as f32;
      }
    }
                texture = Some(sprite.texture().clone());

    result.insert(texture.unwrap(), buf);
                x += glyph.dwidth_x as f32;
                y += glyph.dwidth_y as f32;
            }
        }

    result.update_vbuf()
  }
        result.insert(texture.unwrap(), buf);

        result.update_vbuf()
    }
}

#[derive(Clone, Copy)]
struct Vertex {
  xyz: [f32; 3],
  uv: [f32; 2],
  color: [f32; 4],
  size: [f32; 2],
  uv_size: [f32; 2],
    xyz: [f32; 3],
    uv: [f32; 2],
    color: [f32; 4],
    size: [f32; 2],
    uv_size: [f32; 2],
}

implement_vertex!(Vertex, xyz, uv, color, size, uv_size);

impl FontRenderer for BakedFont<'_> {
  fn draw_strings(&self, surface: &mut AnySurface, mvp: &Mat4, strs: &[(&str, f32, f32, Color)]) {
    self.create_data(strs).draw(surface, mvp);
  }

  fn get_height(&self) -> f32 {
    self.bdf.height as f32
  }

  fn get_char_width(&self, c: char) -> f32 {
    self.bdf.glyph_or_default(c).width as f32
  }

  fn get_str_width(&self, s: &str) -> f32 {
    if s.len() == 0 {
      0.0
    } else {
      s.chars().rev().skip(1).fold(0, |acc, c| acc + self.bdf.glyph_or_default(c).dwidth_x) as f32 +
        self.get_char_width(s.chars().last().unwrap())
    fn draw_strings(&self, surface: &mut AnySurface, mvp: &Mat4, strs: &[(&str, f32, f32, Color)]) {
        self.create_data(strs).draw(surface, mvp);
    }

    fn get_height(&self) -> f32 {
        self.bdf.height as f32
    }

    fn get_char_width(&self, c: char) -> f32 {
        self.bdf.glyph_or_default(c).width as f32
    }

    fn get_str_width(&self, s: &str) -> f32 {
        if s.len() == 0 {
            0.0
        } else {
            s.chars().rev().skip(1).fold(0, |acc, c| acc + self.bdf.glyph_or_default(c).dwidth_x) as f32 +
                self.get_char_width(s.chars().last().unwrap())
        }
    }
  }
}

pub struct BakedString<'a> {
  facade: &'a dyn Facade,
  prog: Rc<Program>,
  data: Vec<(Rc<Texture2d>, Vec<Vertex>, Option<VertexBuffer<Vertex>>)>,
    facade: &'a dyn Facade,
    prog: Rc<Program>,
    data: Vec<(Rc<Texture2d>, Vec<Vertex>, Option<VertexBuffer<Vertex>>)>,
}

impl BakedString<'_> {
  fn insert(&mut self, tex: Rc<Texture2d>, data: Vec<Vertex>) {
    match self.data.iter_mut().find(|(a, ..)| a.get_id() == tex.get_id()) {
      None => { self.data.push((tex, data, None)); }
      Some((_, vec, opt)) => {
        vec.extend_from_slice(&data);
        *opt = None;
      }
    fn insert(&mut self, tex: Rc<Texture2d>, data: Vec<Vertex>) {
        match self.data.iter_mut().find(|(a, ..)| a.get_id() == tex.get_id()) {
            None => { self.data.push((tex, data, None)); }
            Some((_, vec, opt)) => {
                vec.extend_from_slice(&data);
                *opt = None;
            }
        }
    }
  }

  pub fn combine(mut self, other: Self) -> Self {
    for (tex, entry, _) in other.data {
      self.insert(tex, entry);
    pub fn combine(mut self, other: Self) -> Self {
        for (tex, entry, _) in other.data {
            self.insert(tex, entry);
        }
        self
    }
    self
  }

  pub fn update_vbuf(mut self) -> Self {
    for (_, entry, vbuf) in self.data.iter_mut() {
      if vbuf.is_none() {
        *vbuf = Some(VertexBuffer::new(self.facade, entry).unwrap());
      }

    pub fn update_vbuf(mut self) -> Self {
        for (_, entry, vbuf) in self.data.iter_mut() {
            if vbuf.is_none() {
                *vbuf = Some(VertexBuffer::new(self.facade, entry).unwrap());
            }
        }
        self
    }
    self
  }

  pub fn translate(mut self, offset: Vec3) -> Self {
    self.data.iter_mut()
      .flat_map(|(_, v, _)| v.iter_mut())
      .for_each(|Vertex { xyz, .. }| {
        xyz[0] += offset.x();
        xyz[1] += offset.y();
        xyz[2] += offset.z();
      });

    self.data.iter_mut().for_each(|(_, _, vbuf)| *vbuf = None);

    self
  }

  pub fn draw(&self, surface: &mut AnySurface, mvp: &Mat4) {
    let mut dp = DrawParameters::default();
    dp.depth.write = true;
    dp.depth.test = DepthTest::IfLessOrEqual;

    for (tex, _, vbuf) in self.data.iter() {
      if let Some(vbuf) = vbuf {
        surface.draw(
          vbuf,
          NoIndices(PrimitiveType::Points),
          &self.prog,
          &UniformsStorage::new("mvp", *mvp.as_ref())
            .add("texture", &**tex),
          &dp);
      }

    pub fn translate(mut self, offset: Vec3) -> Self {
        self.data.iter_mut()
            .flat_map(|(_, v, _)| v.iter_mut())
            .for_each(|Vertex { xyz, .. }| {
                xyz[0] += offset.x();
                xyz[1] += offset.y();
                xyz[2] += offset.z();
            });

        self.data.iter_mut().for_each(|(_, _, vbuf)| *vbuf = None);

        self
    }

    pub fn draw(&self, surface: &mut AnySurface, mvp: &Mat4) {
        let mut dp = DrawParameters::default();
        dp.depth.write = true;
        dp.depth.test = DepthTest::IfLessOrEqual;

        for (tex, _, vbuf) in self.data.iter() {
            if let Some(vbuf) = vbuf {
                surface.draw(
                    vbuf,
                    NoIndices(PrimitiveType::Points),
                    &self.prog,
                    &UniformsStorage::new("mvp", *mvp.as_ref())
                        .add("texture", &**tex),
                    &dp);
            }
        }
    }
  }
}

M src/font/bdf/mod.rs => src/font/bdf/mod.rs +20 -20
@@ 9,32 9,32 @@ mod parser;

#[derive(Clone)]
pub struct Bdf {
  font_version: String,
  glyphs: HashMap<char, Glyph>,
  default_glyph: char,
  width: u32,
  height: u32,
    font_version: String,
    glyphs: HashMap<char, Glyph>,
    default_glyph: char,
    width: u32,
    height: u32,
}

impl Bdf {
  pub fn glyph(&self, c: char) -> Option<&Glyph> {
    self.glyphs.get(&c)
  }
    pub fn glyph(&self, c: char) -> Option<&Glyph> {
        self.glyphs.get(&c)
    }

  pub fn glyph_or_default(&self, c: char) -> &Glyph {
    self.glyph(c).or_else(|| self.glyph(self.default_glyph)).unwrap()
  }
    pub fn glyph_or_default(&self, c: char) -> &Glyph {
        self.glyph(c).or_else(|| self.glyph(self.default_glyph)).unwrap()
    }
}

#[derive(Clone)]
pub struct Glyph {
  name: String,
  code_point: char,
  width: u32,
  height: u32,
  x_off: i32,
  y_off: i32,
  dwidth_x: u32,
  dwidth_y: u32,
  bits: BitSet,
    name: String,
    code_point: char,
    width: u32,
    height: u32,
    x_off: i32,
    y_off: i32,
    dwidth_x: u32,
    dwidth_y: u32,
    bits: BitSet,
}

M src/font/bdf/parser.rs => src/font/bdf/parser.rs +197 -197
@@ 4,243 4,243 @@ use std::convert::TryInto;
use crate::font::bdf::{Bdf, Glyph};

pub fn parse(lines: &[&str]) -> Result<Bdf, Error> {
  let mut parser = Parser::default();

  for (i, line) in lines.iter().enumerate() {
    if line.trim().is_empty() { continue; }
    parser.line(i, line).map_err(|e| Error { line: i, state: parser.state, desc: e })?;
  }

  if parser.state != ParseState::End {
    Err(Error {
      line: lines.len(),
      state: parser.state,
      desc: ErrorType::Eof,
    })
  } else {
    if !parser.glyphs.contains_key(&parser.default_char) {
      Err(Error {
        line: lines.len(),
        state: parser.state,
        desc: ErrorType::DefaultNotDefined,
      })
    let mut parser = Parser::default();

    for (i, line) in lines.iter().enumerate() {
        if line.trim().is_empty() { continue; }
        parser.line(i, line).map_err(|e| Error { line: i, state: parser.state, desc: e })?;
    }

    if parser.state != ParseState::End {
        Err(Error {
            line: lines.len(),
            state: parser.state,
            desc: ErrorType::Eof,
        })
    } else {
      Ok(Bdf {
        font_version: "2.1".to_owned(),
        glyphs: parser.glyphs,
        default_glyph: parser.default_char,
        width: parser.gwidth,
        height: parser.gheight,
      })
        if !parser.glyphs.contains_key(&parser.default_char) {
            Err(Error {
                line: lines.len(),
                state: parser.state,
                desc: ErrorType::DefaultNotDefined,
            })
        } else {
            Ok(Bdf {
                font_version: "2.1".to_owned(),
                glyphs: parser.glyphs,
                default_glyph: parser.default_char,
                width: parser.gwidth,
                height: parser.gheight,
            })
        }
    }
  }
}

struct Parser {
  state: ParseState,
  bit_line: u32,
  gwidth: u32,
  gheight: u32,
  gxoff: i32,
  gyoff: i32,
  default_char: char,
  current_glyph: Option<Glyph>,
  glyphs: HashMap<char, Glyph>,
    state: ParseState,
    bit_line: u32,
    gwidth: u32,
    gheight: u32,
    gxoff: i32,
    gyoff: i32,
    default_char: char,
    current_glyph: Option<Glyph>,
    glyphs: HashMap<char, Glyph>,
}

impl Default for Parser {
  fn default() -> Self {
    Parser {
      state: Default::default(),
      bit_line: 0,
      gwidth: 0,
      gheight: 0,
      gxoff: 0,
      gyoff: 0,
      default_char: '?',
      current_glyph: None,
      glyphs: Default::default(),
    fn default() -> Self {
        Parser {
            state: Default::default(),
            bit_line: 0,
            gwidth: 0,
            gheight: 0,
            gxoff: 0,
            gyoff: 0,
            default_char: '?',
            current_glyph: None,
            glyphs: Default::default(),
        }
    }
  }
}

impl Parser {
  fn line(&mut self, i: usize, text: &str) -> Result<(), ErrorType> {
    match self.state {
      ParseState::Begin => self.handle_begin(text, i),
      ParseState::Header => self.handle_header(text, i),
      ParseState::InterChar => self.handle_inter_char(text, i),
      ParseState::CharDef => self.handle_char_def(text, i),
      ParseState::CharBits => self.handle_char_bits(text, i),
      ParseState::End => Err(ErrorType::NoEof),
    fn line(&mut self, i: usize, text: &str) -> Result<(), ErrorType> {
        match self.state {
            ParseState::Begin => self.handle_begin(text, i),
            ParseState::Header => self.handle_header(text, i),
            ParseState::InterChar => self.handle_inter_char(text, i),
            ParseState::CharDef => self.handle_char_def(text, i),
            ParseState::CharBits => self.handle_char_bits(text, i),
            ParseState::End => Err(ErrorType::NoEof),
        }
    }
  }

  fn handle_begin(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
    let vs: Vec<&str> = text.split_whitespace().collect();
    if vs.len() != 2 {
      return Err(ErrorType::SyntaxError { desc: "Expected 'STARTFONT 2.1'".to_owned() });
    fn handle_begin(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
        let vs: Vec<&str> = text.split_whitespace().collect();
        if vs.len() != 2 {
            return Err(ErrorType::SyntaxError { desc: "Expected 'STARTFONT 2.1'".to_owned() });
        }
        expect(i, vs[0], "STARTFONT")?;
        expect(i, vs[1], "2.1")?;
        self.state = ParseState::Header;
        Ok(())
    }
    expect(i, vs[0], "STARTFONT")?;
    expect(i, vs[1], "2.1")?;
    self.state = ParseState::Header;
    Ok(())
  }

  fn handle_header(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
    let tokens: Vec<&str> = text.split_whitespace().collect();
    match tokens[0] {
      "FONT" | "COMMENT" | "COPYRIGHT" | "RESOLUTION_X" | "AVERAGE_WIDTH" |
      "NOTICE" | "FOUNDRY" | "SIZE" | "STARTPROPERTIES" | "MIN_SPACE" |
      "ENDPROPERTIES" | "FAMILY_NAME" | "RESOLUTION_Y" | "FONT_DESCENT" |
      "SLANT" | "WEIGHT_NAME" | "PIXEL_SIZE" | "FONT_ASCENT" | "SPACING" |
      "POINT_SIZE" | "ADD_STYLE_NAME" | "SETWIDTH_NAME" | "CHARSET_REGISTRY" |
      "CHARSET_ENCODING" => (),

      "FONTBOUNDINGBOX" => {
        self.gwidth = tokens[1].parse().map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[1]) })?;
        self.gheight = tokens[2].parse().map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[2]) })?;
        self.gxoff = tokens[3].parse().map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[2]) })?;
        self.gyoff = tokens[4].parse().map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[2]) })?;
      }

      "DEFAULT_CHAR" => {
        self.default_char = std::char::from_u32(tokens[1].parse::<u32>()
          .map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[1]) })?).unwrap();
      }

      "CHARS" => {
        self.state = ParseState::InterChar;
      }

      _ => unknown(i, tokens[0])

    fn handle_header(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
        let tokens: Vec<&str> = text.split_whitespace().collect();
        match tokens[0] {
            "FONT" | "COMMENT" | "COPYRIGHT" | "RESOLUTION_X" | "AVERAGE_WIDTH" |
            "NOTICE" | "FOUNDRY" | "SIZE" | "STARTPROPERTIES" | "MIN_SPACE" |
            "ENDPROPERTIES" | "FAMILY_NAME" | "RESOLUTION_Y" | "FONT_DESCENT" |
            "SLANT" | "WEIGHT_NAME" | "PIXEL_SIZE" | "FONT_ASCENT" | "SPACING" |
            "POINT_SIZE" | "ADD_STYLE_NAME" | "SETWIDTH_NAME" | "CHARSET_REGISTRY" |
            "CHARSET_ENCODING" => (),

            "FONTBOUNDINGBOX" => {
                self.gwidth = tokens[1].parse().map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[1]) })?;
                self.gheight = tokens[2].parse().map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[2]) })?;
                self.gxoff = tokens[3].parse().map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[2]) })?;
                self.gyoff = tokens[4].parse().map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[2]) })?;
            }

            "DEFAULT_CHAR" => {
                self.default_char = std::char::from_u32(tokens[1].parse::<u32>()
                    .map_err(|e| ErrorType::SyntaxError { desc: format!("Expected integer, got {}", tokens[1]) })?).unwrap();
            }

            "CHARS" => {
                self.state = ParseState::InterChar;
            }

            _ => unknown(i, tokens[0])
        }
        Ok(())
    }
    Ok(())
  }

  fn handle_inter_char(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
    let tokens: Vec<&str> = text.split_whitespace().collect();
    match tokens[0] {
      "STARTCHAR" => {
        self.state = ParseState::CharDef;
        self.current_glyph = Some(Glyph {
          name: tokens[1].to_owned(),
          code_point: Default::default(),
          width: self.gwidth,
          height: self.gheight,
          x_off: self.gxoff,
          y_off: self.gyoff,
          dwidth_x: 0,
          dwidth_y: 0,
          bits: Default::default(),
        });
      }

      "ENDFONT" => {
        self.state = ParseState::End;
      }

      _ => unknown(i, tokens[0])

    fn handle_inter_char(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
        let tokens: Vec<&str> = text.split_whitespace().collect();
        match tokens[0] {
            "STARTCHAR" => {
                self.state = ParseState::CharDef;
                self.current_glyph = Some(Glyph {
                    name: tokens[1].to_owned(),
                    code_point: Default::default(),
                    width: self.gwidth,
                    height: self.gheight,
                    x_off: self.gxoff,
                    y_off: self.gyoff,
                    dwidth_x: 0,
                    dwidth_y: 0,
                    bits: Default::default(),
                });
            }

            "ENDFONT" => {
                self.state = ParseState::End;
            }

            _ => unknown(i, tokens[0])
        }
        Ok(())
    }
    Ok(())
  }

  fn handle_char_def(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
    let mut tokens = text.split_whitespace();
    match tokens.next() {
      Some("SWIDTH") => {}

      Some("ENCODING") => self.current_glyph.as_mut().unwrap().code_point = tokens.next().unwrap().parse::<u32>().unwrap().try_into().unwrap(),
      Some("DWIDTH") => {
        let g = self.current_glyph.as_mut().unwrap();
        g.dwidth_x = tokens.next().unwrap().parse().unwrap();
        g.dwidth_y = tokens.next().unwrap().parse().unwrap();
      }
      Some("BBX") => {
        let g = self.current_glyph.as_mut().unwrap();
        g.width = tokens.next().unwrap().parse().unwrap();
        g.height = tokens.next().unwrap().parse().unwrap();
        g.x_off = tokens.next().unwrap().parse().unwrap();
        g.y_off = tokens.next().unwrap().parse().unwrap();
      }
      Some("BITMAP") => {
        self.state = ParseState::CharBits;
        self.bit_line = 0;
      }
      _ => {}

    fn handle_char_def(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
        let mut tokens = text.split_whitespace();
        match tokens.next() {
            Some("SWIDTH") => {}

            Some("ENCODING") => self.current_glyph.as_mut().unwrap().code_point = tokens.next().unwrap().parse::<u32>().unwrap().try_into().unwrap(),
            Some("DWIDTH") => {
                let g = self.current_glyph.as_mut().unwrap();
                g.dwidth_x = tokens.next().unwrap().parse().unwrap();
                g.dwidth_y = tokens.next().unwrap().parse().unwrap();
            }
            Some("BBX") => {
                let g = self.current_glyph.as_mut().unwrap();
                g.width = tokens.next().unwrap().parse().unwrap();
                g.height = tokens.next().unwrap().parse().unwrap();
                g.x_off = tokens.next().unwrap().parse().unwrap();
                g.y_off = tokens.next().unwrap().parse().unwrap();
            }
            Some("BITMAP") => {
                self.state = ParseState::CharBits;
                self.bit_line = 0;
            }
            _ => {}
        }
        Ok(())
    }
    Ok(())
  }

  fn handle_char_bits(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
    let glyph = self.current_glyph.as_mut().unwrap();
    if text == "ENDCHAR" {
      self.state = ParseState::InterChar;
      if self.bit_line != glyph.height {
        panic!("Not enough bitmap data! (Got {}, expected {}", self.bit_line, glyph.height)
      }
      let glyph = self.current_glyph.take().unwrap();
      self.glyphs.insert(glyph.code_point, glyph);
    } else {
      let wordlen = (text.len() * 4 - 1) as u32;
      let data = u32::from_str_radix(text, 16).unwrap();
      for i in 0..glyph.width {
        if data >> (wordlen - i) & 1 != 0 {
          glyph.bits.insert((i + self.bit_line * glyph.width) as usize);

    fn handle_char_bits(&mut self, text: &str, i: usize) -> Result<(), ErrorType> {
        let glyph = self.current_glyph.as_mut().unwrap();
        if text == "ENDCHAR" {
            self.state = ParseState::InterChar;
            if self.bit_line != glyph.height {
                panic!("Not enough bitmap data! (Got {}, expected {}", self.bit_line, glyph.height)
            }
            let glyph = self.current_glyph.take().unwrap();
            self.glyphs.insert(glyph.code_point, glyph);
        } else {
            let wordlen = (text.len() * 4 - 1) as u32;
            let data = u32::from_str_radix(text, 16).unwrap();
            for i in 0..glyph.width {
                if data >> (wordlen - i) & 1 != 0 {
                    glyph.bits.insert((i + self.bit_line * glyph.width) as usize);
                }
            }
            self.bit_line += 1;
        }
      }
      self.bit_line += 1;
        Ok(())
    }
    Ok(())
  }
}

fn expect<'a>(line: usize, value: &'a str, expected: &str) -> Result<&'a str, ErrorType> {
  if value == expected {
    Ok(value)
  } else {
    Err(ErrorType::SyntaxError { desc: format!("Expected {}, got {}!", expected, value) })
  }
    if value == expected {
        Ok(value)
    } else {
        Err(ErrorType::SyntaxError { desc: format!("Expected {}, got {}!", expected, value) })
    }
}

fn unknown(line: usize, value: &str) {
  eprintln!("Unknown token in this context: {}, ignoring", value);
    eprintln!("Unknown token in this context: {}, ignoring", value);
}


#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum ParseState {
  // Start of file. Expect STARTFONT 2.1
  Begin,
  // The header where the global stuff is defined.
  Header,
  // In between char definitions. Expect STARTCHAR
  InterChar,
  // In a char definition where the char-specific stuff is defined.
  CharDef,
  // In a char definition where the bitmap is defined. Expect bitmap data.
  CharBits,
  // End of file. Any more stuff after this throws an exception.
  End,
    // Start of file. Expect STARTFONT 2.1
    Begin,
    // The header where the global stuff is defined.
    Header,
    // In between char definitions. Expect STARTCHAR
    InterChar,
    // In a char definition where the char-specific stuff is defined.
    CharDef,
    // In a char definition where the bitmap is defined. Expect bitmap data.
    CharBits,
    // End of file. Any more stuff after this throws an exception.
    End,
}

impl Default for ParseState {
  fn default() -> Self { ParseState::Begin }
    fn default() -> Self { ParseState::Begin }
}

#[derive(Debug)]
pub struct Error {
  line: usize,
  state: ParseState,
  desc: ErrorType,
    line: usize,
    state: ParseState,
    desc: ErrorType,
}

#[derive(Debug)]
pub enum ErrorType {
  Eof,
  NoEof,
  SyntaxError {
    desc: String,
  },
  DefaultNotDefined,
    Eof,
    NoEof,
    SyntaxError {
        desc: String,
    },
    DefaultNotDefined,
}
\ No newline at end of file

M src/font/loader.rs => src/font/loader.rs +28 -28
@@ 8,58 8,58 @@ use crate::util::{AnySurface, Color};
pub type Multifont<'a> = super::multifont::Multifont<FontReq, FontDesc, FontRenderers<'a>, SelectorImpl>;

pub struct FontReq {
  size: u32,
  style: FontStyle,
  dpi: f32,
    size: u32,
    style: FontStyle,
    dpi: f32,
}

pub enum FontStyle {
  Normal,
  Bold,
  Italic,
    Normal,
    Bold,
    Italic,
}

pub struct FontDesc {
  size: u32,
  style: FontStyle,
    size: u32,
    style: FontStyle,
}

pub struct SelectorImpl;

impl Selector<FontReq, FontDesc> for SelectorImpl {
  fn select(&self, requested: &FontReq, test: &FontDesc) -> i32 {
    unimplemented!()
  }
    fn select(&self, requested: &FontReq, test: &FontDesc) -> i32 {
        unimplemented!()
    }
}

pub fn load_multifont(source: &str) -> Multifont {
  let mut fonts = vec![];
    let mut fonts = vec![];

  let cd = CommandDispatcher::new(SimpleExecutor::new(|cmd, args| {}));
    let cd = CommandDispatcher::new(SimpleExecutor::new(|cmd, args| {}));

  Multifont::new(fonts, SelectorImpl)
    Multifont::new(fonts, SelectorImpl)
}

pub enum FontRenderers<'a> {
  Bdf(BakedFont<'a>),
    Bdf(BakedFont<'a>),
}

impl<'a> FontRenderer for FontRenderers<'a> {
  fn draw_strings(&self, surface: &mut AnySurface, mvp: &Mat4, strs: &[(&str, f32, f32, Color)]) {
    match self {
      FontRenderers::Bdf(f) => f.draw_strings(surface, mvp, strs),
    fn draw_strings(&self, surface: &mut AnySurface, mvp: &Mat4, strs: &[(&str, f32, f32, Color)]) {
        match self {
            FontRenderers::Bdf(f) => f.draw_strings(surface, mvp, strs),
        }
    }
  }

  fn get_height(&self) -> f32 {
    unimplemented!()
  }
    fn get_height(&self) -> f32 {
        unimplemented!()
    }

  fn get_char_width(&self, c: char) -> f32 {
    unimplemented!()
  }
    fn get_char_width(&self, c: char) -> f32 {
        unimplemented!()
    }

  fn get_str_width(&self, s: &str) -> f32 {
    unimplemented!()
  }
    fn get_str_width(&self, s: &str) -> f32 {
        unimplemented!()
    }
}
\ No newline at end of file

M src/font/mod.rs => src/font/mod.rs +10 -10
@@ 7,19 7,19 @@ pub mod multifont;
pub mod loader;

pub trait FontRenderer {
  fn draw_char(&self, surface: &mut AnySurface, mvp: &Mat4, c: char, x: f32, y: f32, color: Color) {
    self.draw_string(surface, mvp, &c.to_string(), x, y, color);
  }
    fn draw_char(&self, surface: &mut AnySurface, mvp: &Mat4, c: char, x: f32, y: f32, color: Color) {
        self.draw_string(surface, mvp, &c.to_string(), x, y, color);
    }

  fn draw_string(&self, surface: &mut AnySurface, mvp: &Mat4, s: &str, x: f32, y: f32, color: Color) {
    self.draw_strings(surface, mvp, &[(s, x, y, color)]);
  }
    fn draw_string(&self, surface: &mut AnySurface, mvp: &Mat4, s: &str, x: f32, y: f32, color: Color) {
        self.draw_strings(surface, mvp, &[(s, x, y, color)]);
    }

  fn draw_strings(&self, surface: &mut AnySurface, mvp: &Mat4, strs: &[(&str, f32, f32, Color)]);
    fn draw_strings(&self, surface: &mut AnySurface, mvp: &Mat4, strs: &[(&str, f32, f32, Color)]);

  fn get_height(&self) -> f32;
    fn get_height(&self) -> f32;

  fn get_char_width(&self, c: char) -> f32;
    fn get_char_width(&self, c: char) -> f32;

  fn get_str_width(&self, s: &str) -> f32;
    fn get_str_width(&self, s: &str) -> f32;
}
\ No newline at end of file

M src/font/multifont.rs => src/font/multifont.rs +23 -23
@@ 5,38 5,38 @@ use itertools::Itertools;
use crate::font::FontRenderer;

pub struct Multifont<R, P, T: FontRenderer, S: Selector<R, P>> {
  fonts: Vec<(P, T)>,
  selector: S,
  _phantom_data: PhantomData<R>,
    fonts: Vec<(P, T)>,
    selector: S,
    _phantom_data: PhantomData<R>,
}

impl<R, P, T: FontRenderer, S: Selector<R, P>> Multifont<R, P, T, S> {
  pub fn new(fonts: Vec<(P, T)>, selector: S) -> Self {
    Multifont { fonts, selector, _phantom_data: PhantomData }
  }

  pub fn choose(&self, props: R) -> &T {
    &self.fonts.iter()
      .sorted_by_key(|f| -self.selector.select(&props, &f.0))
      .next().unwrap().1
  }
    pub fn new(fonts: Vec<(P, T)>, selector: S) -> Self {
        Multifont { fonts, selector, _phantom_data: PhantomData }
    }

    pub fn choose(&self, props: R) -> &T {
        &self.fonts.iter()
            .sorted_by_key(|f| -self.selector.select(&props, &f.0))
            .next().unwrap().1
    }
}

pub trait Selector<R, P> {
  fn select(&self, requested: &R, test: &P) -> i32;
    fn select(&self, requested: &R, test: &P) -> i32;
}

pub fn selector<R, P>(op: impl Fn(&R, &P) -> i32) -> impl Selector<R, P> {
  struct Impl<R, P, F: Fn(&R, &P) -> i32> {
    op: F,
    _phantom_data: PhantomData<(R, P)>,
  }

  impl<R, P, F: Fn(&R, &P) -> i32> Selector<R, P> for Impl<R, P, F> {
    fn select(&self, requested: &R, test: &P) -> i32 {
      (self.op)(requested, test)
    struct Impl<R, P, F: Fn(&R, &P) -> i32> {
        op: F,
        _phantom_data: PhantomData<(R, P)>,
    }

    impl<R, P, F: Fn(&R, &P) -> i32> Selector<R, P> for Impl<R, P, F> {
        fn select(&self, requested: &R, test: &P) -> i32 {
            (self.op)(requested, test)
        }
    }
  }

  Impl { op, _phantom_data: PhantomData }
    Impl { op, _phantom_data: PhantomData }
}
\ No newline at end of file

M src/font/texmap/mod.rs => src/font/texmap/mod.rs +8 -8
@@ 3,15 3,15 @@ use std::collections::HashMap;
use glium::Texture2d;

pub struct TextureMapFont {
  texture: Texture2d,
  char_height: u32,
  char_map: HashMap<char, CharData>,
  spacing: u32,
  space_width: u32,
    texture: Texture2d,
    char_height: u32,
    char_map: HashMap<char, CharData>,
    spacing: u32,
    space_width: u32,
}

struct CharData {
  u: f32,
  v: f32,
  width: u32,
    u: f32,
    v: f32,
    width: u32,
}
\ No newline at end of file

M src/i18n/mod.rs => src/i18n/mod.rs +38 -38
@@ 6,64 6,64 @@ use crate::i18n::Error::MissingEntry;
mod parser;

pub struct I18n {
  loc_strings: HashMap<String, ParameterizedString>,
    loc_strings: HashMap<String, ParameterizedString>,
}

impl I18n {
  pub fn new() -> Self {
    I18n { loc_strings: Default::default() }
  }
    pub fn new() -> Self {
        I18n { loc_strings: Default::default() }
    }

  pub fn add_entry(&mut self, key: &str, string: &str) -> Result<(), parser::Error> {
    self.loc_strings.insert(key.to_owned(), ParameterizedString::from(string)?);
    Ok(())
  }
    pub fn add_entry(&mut self, key: &str, string: &str) -> Result<(), parser::Error> {
        self.loc_strings.insert(key.to_owned(), ParameterizedString::from(string)?);
        Ok(())
    }

  pub fn merge(mut self, other: Self) -> Self {
    self.loc_strings.extend(other.loc_strings.into_iter());
    self
  }
    pub fn merge(mut self, other: Self) -> Self {
        self.loc_strings.extend(other.loc_strings.into_iter());
        self
    }

  pub fn tl<'a>(&'a self, key: &str, params: &[&str]) -> Result<Cow<'a, str>, Error> {
    self.loc_strings.get(key)
      .ok_or(MissingEntry(key.to_owned()))
      .and_then(|ps| ps.fmt(params))
  }
    pub fn tl<'a>(&'a self, key: &str, params: &[&str]) -> Result<Cow<'a, str>, Error> {
        self.loc_strings.get(key)
            .ok_or(MissingEntry(key.to_owned()))
            .and_then(|ps| ps.fmt(params))
    }
}

#[derive(Debug, Clone)]
struct ParameterizedString {
  components: Vec<PStringComponent>,
    components: Vec<PStringComponent>,
}

impl ParameterizedString {
  pub fn fmt<'a>(&'a self, params: &[&str]) -> Result<Cow<'a, str>, Error> {
    match &*self.components {
      &[PStringComponent::Verbatim(ref s)] => Ok(Cow::Borrowed(&s)),
      _ => {
        self.components.iter()
          .map(|c| match c {
            PStringComponent::Verbatim(s) => Ok(&s[..]),
            PStringComponent::Variable(idx) => params.get(*idx).map(|s| *s).ok_or(Error::ParameterCountMismatch),
          })
          .fold(Ok(String::new()), |acc, a| acc.and_then(|mut s| {
            s.push_str(a?);
            Ok(s)
          }))
          .map(|s| Cow::Owned(s))
      }
    pub fn fmt<'a>(&'a self, params: &[&str]) -> Result<Cow<'a, str>, Error> {
        match &*self.components {
            &[PStringComponent::Verbatim(ref s)] => Ok(Cow::Borrowed(&s)),
            _ => {
                self.components.iter()
                    .map(|c| match c {
                        PStringComponent::Verbatim(s) => Ok(&s[..]),
                        PStringComponent::Variable(idx) => params.get(*idx).map(|s| *s).ok_or(Error::ParameterCountMismatch),
                    })
                    .fold(Ok(String::new()), |acc, a| acc.and_then(|mut s| {
                        s.push_str(a?);
                        Ok(s)
                    }))
                    .map(|s| Cow::Owned(s))
            }
        }
    }
  }
}

#[derive(Debug, Clone)]
enum PStringComponent {
  Verbatim(String),
  Variable(usize),
    Verbatim(String),
    Variable(usize),
}

#[derive(Debug)]
pub enum Error {
  MissingEntry(String),
  ParameterCountMismatch,
    MissingEntry(String),
    ParameterCountMismatch,
}
\ No newline at end of file

M src/i18n/parser.rs => src/i18n/parser.rs +169 -169
@@ 9,198 9,198 @@ use crate::res;
use crate::res::ResourceLoader;

impl I18n {
  pub fn parse_from_res(rl: &ResourceLoader, file: impl AsRef<Path>) -> Result<I18n, Error> {
    I18n::parse_from_file(file, &FsInfo { rl, cd: "/".as_ref() })
  }

  pub fn parse(source: &str) -> Result<I18n, Error> {
    I18n::parse_inner(source, None)
  }

  fn parse_from_file(file: impl AsRef<Path>, &FsInfo { rl, cd }: &FsInfo) -> Result<I18n, Error> {
    let full_path = cd.join(file);
    let s = rl.open(&full_path)
      .map_err(|e| Error::IncludeFindError(e))
      .and_then(|mut src| {
        let mut s = String::new();
        src.read_to_string(&mut s).map(|_| s).map_err(|e| Error::IncludeReadError(e))
      })?;
    let parent = full_path.parent().unwrap_or("/".as_ref());
    I18n::parse_inner(&s, Some(&FsInfo { rl, cd: parent }))
  }

  fn parse_inner(source: &str, fs_info: Option<&FsInfo>) -> Result<I18n, Error> {
    fn normalize_multiline(lineno: usize, lines: &[String]) -> Result<String, Error> {
      let first = &*lines[0];
      let result = lines.iter()
        .fold(Some((first.chars().take_while(|c| c.is_whitespace()).count(), first)), |acc, a| {
          match acc {
            x if a.trim().is_empty() => x,
            Some((common_indent, last)) if last.trim().is_empty() => Some((min(common_indent, a.len()), a)),
            Some((mut common_indent, last)) => {
              common_indent = a.chars()
                .take(common_indent)
                .take_while(|c| c.is_whitespace())
                .count();

              if &last[..common_indent] != &a[..common_indent] {
                None
              } else {
                Some((common_indent, a))
              }
            }
            None => None,
          }
        });

      match result {
        Some((common_indent, _)) => {
          let mut fixed_lines = lines.iter()
            .map(|s| if s.trim().is_empty() { "" } else { s[common_indent..].trim_end() })
            .collect::<Vec<_>>();

          while let Some(l) = fixed_lines.last() {
            if !l.is_empty() { break; }
            fixed_lines.pop();
          }
          Ok(fixed_lines.join("\n"))
        }
        None => Err(Error::MultilineIndentError(lineno)),
      }
    pub fn parse_from_res(rl: &ResourceLoader, file: impl AsRef<Path>) -> Result<I18n, Error> {
        I18n::parse_from_file(file, &FsInfo { rl, cd: "/".as_ref() })
    }

    let mut result = I18n { loc_strings: HashMap::new() };
    let mut multiline: Option<(String, Vec<String>)> = None;
    for (lineno, mut line) in source.lines().map(|s| s.trim_end_matches(|c| "\n\r".contains(c))).enumerate() {
      if multiline.is_some() {
        match line.chars().next() {
          Some(x) if !x.is_whitespace() => {
            let (key, string) = multiline.unwrap();
            result.loc_strings.insert(key, ParameterizedString::from(&normalize_multiline(lineno, &string)?)?);
            multiline = None;
          }
          _ => {}
        }
      }

      if let Some(value) = &mut multiline {
        let (_, string) = value;
        string.push(line.to_owned());
      } else {
        if let Some(index) = line.find("//") {
          line = &line[..index];
        }
    pub fn parse(source: &str) -> Result<I18n, Error> {
        I18n::parse_inner(source, None)
    }

        line = line.trim_end();
        match line.chars().next() {
          Some(x) if x.is_whitespace() => return Err(Error::InvalidWhitespace(lineno)),
          None => continue,
          _ => {}
    fn parse_from_file(file: impl AsRef<Path>, &FsInfo { rl, cd }: &FsInfo) -> Result<I18n, Error> {
        let full_path = cd.join(file);
        let s = rl.open(&full_path)
            .map_err(|e| Error::IncludeFindError(e))
            .and_then(|mut src| {
                let mut s = String::new();
                src.read_to_string(&mut s).map(|_| s).map_err(|e| Error::IncludeReadError(e))
            })?;
        let parent = full_path.parent().unwrap_or("/".as_ref());
        I18n::parse_inner(&s, Some(&FsInfo { rl, cd: parent }))
    }

    fn parse_inner(source: &str, fs_info: Option<&FsInfo>) -> Result<I18n, Error> {
        fn normalize_multiline(lineno: usize, lines: &[String]) -> Result<String, Error> {
            let first = &*lines[0];
            let result = lines.iter()
                .fold(Some((first.chars().take_while(|c| c.is_whitespace()).count(), first)), |acc, a| {
                    match acc {
                        x if a.trim().is_empty() => x,
                        Some((common_indent, last)) if last.trim().is_empty() => Some((min(common_indent, a.len()), a)),
                        Some((mut common_indent, last)) => {
                            common_indent = a.chars()
                                .take(common_indent)
                                .take_while(|c| c.is_whitespace())
                                .count();

                            if &last[..common_indent] != &a[..common_indent] {
                                None
                            } else {
                                Some((common_indent, a))
                            }
                        }
                        None => None,
                    }
                });

            match result {
                Some((common_indent, _)) => {
                    let mut fixed_lines = lines.iter()
                        .map(|s| if s.trim().is_empty() { "" } else { s[common_indent..].trim_end() })
                        .collect::<Vec<_>>();

                    while let Some(l) = fixed_lines.last() {
                        if !l.is_empty() { break; }
                        fixed_lines.pop();
                    }
                    Ok(fixed_lines.join("\n"))
                }
                None => Err(Error::MultilineIndentError(lineno)),
            }
        }
        let next_token = line.chars().take_while(|c| !c.is_whitespace()).collect::<String>();
        match &*next_token {
          "include" => {
            let included_file: &Path = line[next_token.len()..].trim().as_ref();
            let included = match fs_info {
              None => return Err(Error::MissingFsInfo),
              Some(fs_info) => I18n::parse_from_file(included_file, fs_info).map_err(|e| Error::ErrorInInclude(lineno, box e)),
            }?;
            result = result.merge(included);
          }
          _ => {
            let vec = line.splitn(2, ":").collect::<Vec<_>>();
            match &*vec {
              &[key, string] if string.trim().is_empty() => {
                multiline = Some((key.to_owned(), vec![]));
              }
              &[key, string] => {
                result.loc_strings.insert(key.to_owned(), ParameterizedString::from(string.trim())?);
              }
              _ => return Err(Error::SyntaxError(lineno)),

        let mut result = I18n { loc_strings: HashMap::new() };
        let mut multiline: Option<(String, Vec<String>)> = None;
        for (lineno, mut line) in source.lines().map(|s| s.trim_end_matches(|c| "\n\r".contains(c))).enumerate() {
            if multiline.is_some() {
                match line.chars().next() {
                    Some(x) if !x.is_whitespace() => {
                        let (key, string) = multiline.unwrap();
                        result.loc_strings.insert(key, ParameterizedString::from(&normalize_multiline(lineno, &string)?)?);
                        multiline = None;
                    }
                    _ => {}
                }
            }

            if let Some(value) = &mut multiline {
                let (_, string) = value;
                string.push(line.to_owned());
            } else {
                if let Some(index) = line.find("//") {
                    line = &line[..index];
                }

                line = line.trim_end();
                match line.chars().next() {
                    Some(x) if x.is_whitespace() => return Err(Error::InvalidWhitespace(lineno)),
                    None => continue,
                    _ => {}
                }
                let next_token = line.chars().take_while(|c| !c.is_whitespace()).collect::<String>();
                match &*next_token {
                    "include" => {
                        let included_file: &Path = line[next_token.len()..].trim().as_ref();
                        let included = match fs_info {
                            None => return Err(Error::MissingFsInfo),
                            Some(fs_info) => I18n::parse_from_file(included_file, fs_info).map_err(|e| Error::ErrorInInclude(lineno, box e)),
                        }?;
                        result = result.merge(included);
                    }
                    _ => {
                        let vec = line.splitn(2, ":").collect::<Vec<_>>();
                        match &*vec {
                            &[key, string] if string.trim().is_empty() => {
                                multiline = Some((key.to_owned(), vec![]));
                            }
                            &[key, string] => {
                                result.loc_strings.insert(key.to_owned(), ParameterizedString::from(string.trim())?);
                            }
                            _ => return Err(Error::SyntaxError(lineno)),
                        }
                    }
                }
            }
          }
        }
      }
    }

    if let Some((key, string)) = multiline {
      result.loc_strings.insert(key, ParameterizedString::from(&normalize_multiline(source.len(), &string)?)?);
    }
        if let Some((key, string)) = multiline {
            result.loc_strings.insert(key, ParameterizedString::from(&normalize_multiline(source.len(), &string)?)?);
        }

    Ok(result)
  }
        Ok(result)
    }
}

impl ParameterizedString {
  pub fn from(s: &str) -> Result<ParameterizedString, Error> {
    let mut components = vec![];
    let mut iter = s.chars();
    let mut cur = String::new();
    let mut counter = 0;
    loop {
      match iter.next() {
        Some('{') => {
          let mut b = String::new();
          let res = loop {
    pub fn from(s: &str) -> Result<ParameterizedString, Error> {
        let mut components = vec![];
        let mut iter = s.chars();
        let mut cur = String::new();
        let mut counter = 0;
        loop {
            match iter.next() {
              Some('}') => break Ok(b),
              Some(c) => b.push(c),
              None => break Err(Error::Eof),
                Some('{') => {
                    let mut b = String::new();
                    let res = loop {
                        match iter.next() {
                            Some('}') => break Ok(b),
                            Some(c) => b.push(c),
                            None => break Err(Error::Eof),
                        }
                    }?;
                    let index = match res.as_str() {
                        "" => {
                            let res = counter;
                            counter += 1;
                            res
                        }
                        x => x.parse()?,
                    };
                    if !cur.is_empty() {
                        components.push(PStringComponent::Verbatim(cur));
                        cur = String::new();
                    }
                    components.push(PStringComponent::Variable(index))
                }
                Some('\\') => {
                    let option = iter.next();
                    match option {
                        Some(c) => cur.push(c),
                        None => break Err(Error::Eof),
                    }
                }
                Some(c) => {
                    cur.push(c);
                }
                None => {
                    if !cur.is_empty() {
                        components.push(PStringComponent::Verbatim(cur));
                    }
                    break Ok(ParameterizedString { components });
                }
            }
          }?;
          let index = match res.as_str() {
            "" => {
              let res = counter;
              counter += 1;
              res
            }
            x => x.parse()?,
          };
          if !cur.is_empty() {
            components.push(PStringComponent::Verbatim(cur));
            cur = String::new();
          }
          components.push(PStringComponent::Variable(index))
        }
        Some('\\') => {
          let option = iter.next();
          match option {
            Some(c) => cur.push(c),
            None => break Err(Error::Eof),
          }
        }
        Some(c) => {
          cur.push(c);
        }
        None => {
          if !cur.is_empty() {
            components.push(PStringComponent::Verbatim(cur));
          }
          break Ok(ParameterizedString { components });
        }
      }
    }
  }
}

struct FsInfo<'a> {
  rl: &'a ResourceLoader,
  cd: &'a Path,
    rl: &'a ResourceLoader,
    cd: &'a Path,
}

#[derive(Debug)]
pub enum Error {
  Eof,
  InvalidIndex(ParseIntError),
  InvalidWhitespace(usize),
  SyntaxError(usize),
  MissingFsInfo,
  IncludeFindError(res::Error),
  IncludeReadError(std::io::Error),
  ErrorInInclude(usize, Box<Error>),
  MultilineIndentError(usize),
    Eof,
    InvalidIndex(ParseIntError),
    InvalidWhitespace(usize),
    SyntaxError(usize),
    MissingFsInfo,
    IncludeFindError(res::Error),
    IncludeReadError(std::io::Error),
    ErrorInInclude(usize, Box<Error>),
    MultilineIndentError(usize),
}

impl From<ParseIntError> for Error {
  fn from(that: ParseIntError) -> Self { Error::InvalidIndex(that) }
    fn from(that: ParseIntError) -> Self { Error::InvalidIndex(that) }
}
\ No newline at end of file

M src/kb.rs => src/kb.rs +28 -28
@@ 1,40 1,40 @@
use glium::glutin::event::{ElementState, KeyboardInput, VirtualKeyCode};

pub struct Keyboard {
  pub fwd: bool,
  pub back: bool,
  pub left: bool,
  pub right: bool,
  pub esc: bool,
    pub fwd: bool,
    pub back: bool,
    pub left: bool,
    pub right: bool,
    pub esc: bool,
}

impl Keyboard {
  pub fn new() -> Self {
    Keyboard {
      fwd: false,
      back: false,
      left: false,
      right: false,
      esc: false,
    pub fn new() -> Self {
        Keyboard {
            fwd: false,
            back: false,
            left: false,
            right: false,
            esc: false,
        }
    }
  }

  pub fn handle_event(&mut self, evt: &KeyboardInput) {
    let ptr = match evt.virtual_keycode {
      Some(VirtualKeyCode::W) => Some(&mut self.fwd),
      Some(VirtualKeyCode::S) => Some(&mut self.back),
      Some(VirtualKeyCode::A) => Some(&mut self.left),
      Some(VirtualKeyCode::D) => Some(&mut self.right),
      Some(VirtualKeyCode::Escape) => Some(&mut self.esc),
      _ => None,
    };
    pub fn handle_event(&mut self, evt: &KeyboardInput) {
        let ptr = match evt.virtual_keycode {
            Some(VirtualKeyCode::W) => Some(&mut self.fwd),
            Some(VirtualKeyCode::S) => Some(&mut self.back),
            Some(VirtualKeyCode::A) => Some(&mut self.left),
            Some(VirtualKeyCode::D) => Some(&mut self.right),
            Some(VirtualKeyCode::Escape) => Some(&mut self.esc),
            _ => None,
        };

    if let Some(ptr) = ptr {
      *ptr = evt.state == ElementState::Pressed;
        if let Some(ptr) = ptr {
            *ptr = evt.state == ElementState::Pressed;
        }
    }
  }

  pub fn reset_all(&mut self) {
    *self = Keyboard::new();
  }
    pub fn reset_all(&mut self) {
        *self = Keyboard::new();
    }
}
\ No newline at end of file

M src/main.rs => src/main.rs +443 -443
@@ 70,276 70,276 @@ const GAME_NAME: &str = "Untitled Game";
const GAME_VERSION: &str = env!("CARGO_PKG_VERSION");

fn main() {
  let mut data_dir: Option<Vec<_>> = None;
  let mut save_dir = None;
  let mut v_sync = false;
  let mut language = None;
  let mut init_script = None;
  let mut data_merge = MergeModeTable::new();

  {
    let mut cd = CommandDispatcher::new(SimpleExecutor::new(|cmd, args| {
      match cmd {
        "data_dir" => data_dir = Some(args.iter().map(|el| PathBuf::from(el)).collect()),
        "save_dir" => save_dir = Some(PathBuf::from(args[0])),
        "v_sync" => v_sync = args[0].parse::<i32>().unwrap_or(0) != 0,
        "language" => language = Some(args.iter().map(|&s| s.to_owned()).collect::<Vec<_>>()),
        "data_merge_mode" => data_merge.add_entry(args[0], args[1].try_into().unwrap()),
        "exec_init" => init_script = Some(PathBuf::from(args[0])),
        _ => eprintln!("Ignoring invalid bootstrap command '{}'", cmd),
      }
    }));
    cd.scheduler().exec_path("boot.cfg", ExecSource::Event);
    cd.resume_until_empty();
  }

  let data_dir = data_dir.expect("data_dir was not set!");
  let save_dir = save_dir.expect("save_dir was not set!");
  let language = language.expect("language was not set!");
  let init_script = init_script.expect("exec_init was not set!");

  create_dir_all(&save_dir).expect("Could not create save directory.");

  let logfile = File::create(save_dir.join("system.log")).expect("Failed to create log file.");

  CombinedLogger::init(vec![
    TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed).unwrap(),
    WriteLogger::new(LevelFilter::Debug, ConfigBuilder::new().set_time_format_str("%Y-%m-%d %H:%M:%S").build(), logfile),
  ]).unwrap();

  info!("Data directory: {}", data_dir.iter().map(|el| format!("'{}'", el.to_string_lossy())).join(" "));
  info!("Save directory: '{}'", save_dir.as_os_str().to_string_lossy());

  let sources = data_dir.iter().map(|el| {
    let is_res_file = el.extension().and_then(|s| s.to_str()).map_or(false, |s| s == "res");
    if is_res_file { DataSource::Archive(el.clone()) } else { DataSource::Filesystem(el.clone()) }
  }).collect();

  let rl = ResourceLoader::new(data_merge, sources);
  let i18n = language.into_iter()
    .map(|p| I18n::parse_from_res(&rl, &format!("strs/{}.str", p)).unwrap())
    .fold(I18n::new(), |acc, a| acc.merge(a));

  start_game(Environment { rl, i18n, data_dir, save_dir, v_sync, init_script });
}

fn start_game(env: Environment) -> ! {
  let env = Rc::new(env);
  let title = format!("{} v{} {}", GAME_NAME, GAME_VERSION, if cfg!(debug_assertions) { "Debug" } else { "Release" });

  let mut event_loop = EventLoop::new();
  let display = glium::Display::new(
    WindowBuilder::new()
      .with_title(&title)
      .with_visible(true)
      .with_inner_size(LogicalSize::new(640.0, 480.0)),
    ContextBuilder::new()
      .with_gl(GlRequest::Specific(OpenGl, (4, 0)))
      .with_gl_profile(GlProfile::Core)
      .with_vsync(env.v_sync),
    &event_loop,
  ).expect("Failed to create window");

  let gl_window = display.gl_window();
  let gl_window: &glium::glutin::window::Window = gl_window.window();

  let running = Arc::new(AtomicBool::new(true));

  let ui_settings = Rc::new(Cell::new(UiSettings::default()));

  let mut ce = {
    fn parse_color(args: &[&str]) -> Color {
      let r = args.get(0).unwrap_or(&"").parse().unwrap_or(0);
      let g = args.get(1).unwrap_or(&"").parse().unwrap_or(0);
      let b = args.get(2).unwrap_or(&"").parse().unwrap_or(0);
      Color::from_rgb8(r, g, b)
    let mut data_dir: Option<Vec<_>> = None;
    let mut save_dir = None;
    let mut v_sync = false;
    let mut language = None;
    let mut init_script = None;
    let mut data_merge = MergeModeTable::new();

    {
        let mut cd = CommandDispatcher::new(SimpleExecutor::new(|cmd, args| {
            match cmd {
                "data_dir" => data_dir = Some(args.iter().map(|el| PathBuf::from(el)).collect()),
                "save_dir" => save_dir = Some(PathBuf::from(args[0])),
                "v_sync" => v_sync = args[0].parse::<i32>().unwrap_or(0) != 0,
                "language" => language = Some(args.iter().map(|&s| s.to_owned()).collect::<Vec<_>>()),
                "data_merge_mode" => data_merge.add_entry(args[0], args[1].try_into().unwrap()),
                "exec_init" => init_script = Some(PathBuf::from(args[0])),
                _ => eprintln!("Ignoring invalid bootstrap command '{}'", cmd),
            }
        }));
        cd.scheduler().exec_path("boot.cfg", ExecSource::Event);
        cd.resume_until_empty();
    }

    let env = env.clone();
    let running = running.clone();
    EngineBuilder::new(LogPipe::info())
      .with_default()
      .with_cvar("test_var", ConVar::of_num(10.0, Some(0.0), Some(20.0)))
      .with_help_text("test_var", "a test variable")
      .with_cvar("string_test", ConVar::of_string("yeah"))
      .with_command("quit", box command(move |_, _, _| { running.store(false, Ordering::Relaxed); }))
      .with_help_text("quit", "quit the game")
      .with_cvar("fps_limit", ConVar::of_int(120, Some(0), None))
      .with_cvar("debug_info", ConVar::of_bool(false))
      .with_help_text("debug_info", "whether to show debug info like fps and player position")
      .with_command("ui_col_border", {
        let ui_settings = ui_settings.clone();
        box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.border_color = parse_color(args))); })
      })
      .with_command("ui_col_normal", {
        let ui_settings = ui_settings.clone();
        box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.normal_color = parse_color(args))); })
      })
      .with_command("ui_col_hilight", {
        let ui_settings = ui_settings.clone();
        box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.highlighted_color = parse_color(args))); })
      })
      .with_command("ui_col_act", {
        let ui_settings = ui_settings.clone();
        box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.active_color = parse_color(args))); })
      })
      .with_command("ui_col_disabled", {
        let ui_settings = ui_settings.clone();
        box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.disabled_color = parse_color(args))); })
      })
      .with_command("ui_col_query", {
        let ui_settings = ui_settings.clone();
        box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.query_text_color = parse_color(args))); })
      })
      .with_command("ui_col_text", {
        let ui_settings = ui_settings.clone();
        box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.text_color = parse_color(args))); })
      })
      .with_command("ui_col_tsel", {
        let ui_settings = ui_settings.clone();
        box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.text_selected_background_color = parse_color(args))); })
      })
      .with_command("exec", box expand_command(move |args, _, out| {
        let file = format!("cfg/{}.cfg", *args.get(0)?);

        let script = std::fs::read_to_string(env.save_dir.join(&file)).ok()
          .or(env.rl.open(&file).ok().and_then(|mut it| {
            let mut script = String::new();
            it.read_to_string(&mut script).ok().map(|_| script)
          }));

        if script.is_none() {
          writeln!(out, "Script not found: '{}'", &file).unwrap();
        }
    let data_dir = data_dir.expect("data_dir was not set!");
    let save_dir = save_dir.expect("save_dir was not set!");
    let language = language.expect("language was not set!");
    let init_script = init_script.expect("exec_init was not set!");

        script
      }))
      .with_help_text("exec", "execute a script")
      .build()
  };
    create_dir_all(&save_dir).expect("Could not create save directory.");

  ce.scheduler().exec(&format!("exec \"{}\"", env.init_script.to_string_lossy()), ExecSource::Event);
  ce.dispatcher_mut().resume_until_empty();
    let logfile = File::create(save_dir.join("system.log")).expect("Failed to create log file.");

  let mut sm = ShaderManager::new(&display, &env.rl);
    CombinedLogger::init(vec![
        TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed).unwrap(),
        WriteLogger::new(LevelFilter::Debug, ConfigBuilder::new().set_time_format_str("%Y-%m-%d %H:%M:%S").build(), logfile),
    ]).unwrap();

  let mut window = {
    let size = gl_window.inner_size();
    Window {
      width: size.width,
      height: size.height,
      dpi: gl_window.scale_factor(),
    }
  };
    info!("Data directory: {}", data_dir.iter().map(|el| format!("'{}'", el.to_string_lossy())).join(" "));
    info!("Save directory: '{}'", save_dir.as_os_str().to_string_lossy());

    let sources = data_dir.iter().map(|el| {
        let is_res_file = el.extension().and_then(|s| s.to_str()).map_or(false, |s| s == "res");
        if is_res_file { DataSource::Archive(el.clone()) } else { DataSource::Filesystem(el.clone()) }
    }).collect();

    let rl = ResourceLoader::new(data_merge, sources);
    let i18n = language.into_iter()
        .map(|p| I18n::parse_from_res(&rl, &format!("strs/{}.str", p)).unwrap())
        .fold(I18n::new(), |acc, a| acc.merge(a));

    start_game(Environment { rl, i18n, data_dir, save_dir, v_sync, init_script });
}

  let model = Rc::new(get_test_model(&mut sm, &display));
fn start_game(env: Environment) -> ! {
    let env = Rc::new(env);
    let title = format!("{} v{} {}", GAME_NAME, GAME_VERSION, if cfg!(debug_assertions) { "Debug" } else { "Release" });

    let mut event_loop = EventLoop::new();
    let display = glium::Display::new(
        WindowBuilder::new()
            .with_title(&title)
            .with_visible(true)
            .with_inner_size(LogicalSize::new(640.0, 480.0)),
        ContextBuilder::new()
            .with_gl(GlRequest::Specific(OpenGl, (4, 0)))
            .with_gl_profile(GlProfile::Core)
            .with_vsync(env.v_sync),
        &event_loop,
    ).expect("Failed to create window");

    let gl_window = display.gl_window();
    let gl_window: &glium::glutin::window::Window = gl_window.window();

    let running = Arc::new(AtomicBool::new(true));

    let ui_settings = Rc::new(Cell::new(UiSettings::default()));

    let mut ce = {
        fn parse_color(args: &[&str]) -> Color {
            let r = args.get(0).unwrap_or(&"").parse().unwrap_or(0);
            let g = args.get(1).unwrap_or(&"").parse().unwrap_or(0);
            let b = args.get(2).unwrap_or(&"").parse().unwrap_or(0);
            Color::from_rgb8(r, g, b)
        }

        let env = env.clone();
        let running = running.clone();
        EngineBuilder::new(LogPipe::info())
            .with_default()
            .with_cvar("test_var", ConVar::of_num(10.0, Some(0.0), Some(20.0)))
            .with_help_text("test_var", "a test variable")
            .with_cvar("string_test", ConVar::of_string("yeah"))
            .with_command("quit", box command(move |_, _, _| { running.store(false, Ordering::Relaxed); }))
            .with_help_text("quit", "quit the game")
            .with_cvar("fps_limit", ConVar::of_int(120, Some(0), None))
            .with_cvar("debug_info", ConVar::of_bool(false))
            .with_help_text("debug_info", "whether to show debug info like fps and player position")
            .with_command("ui_col_border", {
                let ui_settings = ui_settings.clone();
                box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.border_color = parse_color(args))); })
            })
            .with_command("ui_col_normal", {
                let ui_settings = ui_settings.clone();
                box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.normal_color = parse_color(args))); })
            })
            .with_command("ui_col_hilight", {
                let ui_settings = ui_settings.clone();
                box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.highlighted_color = parse_color(args))); })
            })
            .with_command("ui_col_act", {
                let ui_settings = ui_settings.clone();
                box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.active_color = parse_color(args))); })
            })
            .with_command("ui_col_disabled", {
                let ui_settings = ui_settings.clone();
                box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.disabled_color = parse_color(args))); })
            })
            .with_command("ui_col_query", {
                let ui_settings = ui_settings.clone();
                box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.query_text_color = parse_color(args))); })
            })
            .with_command("ui_col_text", {
                let ui_settings = ui_settings.clone();
                box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.text_color = parse_color(args))); })
            })
            .with_command("ui_col_tsel", {
                let ui_settings = ui_settings.clone();
                box command(move |args, _, _| { ui_settings.update(|s| apply(s, |s| s.text_selected_background_color = parse_color(args))); })
            })
            .with_command("exec", box expand_command(move |args, _, out| {
                let file = format!("cfg/{}.cfg", *args.get(0)?);

                let script = std::fs::read_to_string(env.save_dir.join(&file)).ok()
                    .or(env.rl.open(&file).ok().and_then(|mut it| {
                        let mut script = String::new();
                        it.read_to_string(&mut script).ok().map(|_| script)
                    }));

                if script.is_none() {
                    writeln!(out, "Script not found: '{}'", &file).unwrap();
                }

                script
            }))
            .with_help_text("exec", "execute a script")
            .build()
    };

    ce.scheduler().exec(&format!("exec \"{}\"", env.init_script.to_string_lossy()), ExecSource::Event);
    ce.dispatcher_mut().resume_until_empty();

    let mut sm = ShaderManager::new(&display, &env.rl);

    let mut window = {
        let size = gl_window.inner_size();
        Window {
            width: size.width,
            height: size.height,
            dpi: gl_window.scale_factor(),
        }
    };

    let model = Rc::new(get_test_model(&mut sm, &display));

  let mut camera = Rc::new(RefCell::new(Camera::new()));
  let mut kb = Keyboard::new();
  camera.borrow_mut().set_pos(vec3(0.0, 10.0, -5.0));
    let mut camera = Rc::new(RefCell::new(Camera::new()));
    let mut kb = Keyboard::new();
    camera.borrow_mut().set_pos(vec3(0.0, 10.0, -5.0));
//  camera.borrow_mut().set_pitch(-30.0);
  camera.borrow_mut().set_yaw(10.0);

  let fr = load_fonts(&env.rl, &display, &mut sm);

  let mut game = Game {
    env: env.clone(),
    ce,
    running,
    display: &display,
    sm,
    window,
    ui_host: UiHost::new(),
    kb,
    fps: FpsCounter::new(),
    camera,
    fr,
    ui_settings,
  };

  game.ui_host.open_ui(TestUiController::new(game.camera.clone(), model.clone(), game.ce.scheduler().clone()));

  event_loop.run_return(|event, target, cf| game.handle_event(event, target, cf));
  exit(0);
    camera.borrow_mut().set_yaw(10.0);

    let fr = load_fonts(&env.rl, &display, &mut sm);

    let mut game = Game {
        env: env.clone(),
        ce,
        running,
        display: &display,
        sm,
        window,
        ui_host: UiHost::new(),
        kb,
        fps: FpsCounter::new(),
        camera,
        fr,
        ui_settings,
    };

    game.ui_host.open_ui(TestUiController::new(game.camera.clone(), model.clone(), game.ce.scheduler().clone()));

    event_loop.run_return(|event, target, cf| game.handle_event(event, target, cf));
    exit(0);
}

pub struct GameContainer<'a> {
  event_loop: EventLoop<()>,
  game: Game<'a>,
    event_loop: EventLoop<()>,
    game: Game<'a>,
}

pub struct Game<'a> {
  env: Rc<Environment>,
  ce: ConsoleEngine<LogPipe<'static>>,
  running: Arc<AtomicBool>,
  display: &'a Display,
  sm: ShaderManager<'a>,
  window: Window,
  ui_host: UiHost,
  kb: Keyboard,
  fps: FpsCounter,
  camera: Rc<RefCell<Camera>>,
  fr: Multifont<(i32, bool, f64), (i32, bool), BakedFont<'a>, SelImpl>,
  ui_settings: Rc<Cell<UiSettings>>,
    env: Rc<Environment>,
    ce: ConsoleEngine<LogPipe<'static>>,
    running: Arc<AtomicBool>,
    display: &'a Display,
    sm: ShaderManager<'a>,
    window: Window,
    ui_host: UiHost,
    kb: Keyboard,
    fps: FpsCounter,
    camera: Rc<RefCell<Camera>>,
    fr: Multifont<(i32, bool, f64), (i32, bool), BakedFont<'a>, SelImpl>,
    ui_settings: Rc<Cell<UiSettings>>,
}

const FONT_SIZE: i32 = 16;

impl Game<'_> {
  fn handle_event(&mut self, event: Event<()>, target: &EventLoopWindowTarget<()>, cf: &mut ControlFlow) {
    match event {
      Event::RedrawRequested(win) => {
        self.cycle(false);
      }
      Event::MainEventsCleared => {
        if self.ui_host.is_animated() {
          self.cycle(true);
        }
      }
      Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
        self.running.store(false, Ordering::Relaxed);
      }
      Event::WindowEvent { event: WindowEvent::Resized(PhysicalSize { width, height }), .. } => {
        self.window.on_resize(width, height);
        self.ui_host.resize(self.window.width(), self.window.height());
      }
      Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, .. } => {
        self.ui_host.on_key(input.state, input.virtual_keycode, input.scancode, input.modifiers);
      }
      Event::WindowEvent { event: WindowEvent::ReceivedCharacter(c), .. } => {
        self.ui_host.on_key_typed(c);
      }
      Event::WindowEvent { event: WindowEvent::Focused(state), .. } => {
    fn handle_event(&mut self, event: Event<()>, target: &EventLoopWindowTarget<()>, cf: &mut ControlFlow) {
        match event {
            Event::RedrawRequested(win) => {
                self.cycle(false);
            }
            Event::MainEventsCleared => {
                if self.ui_host.is_animated() {
                    self.cycle(true);
                }
            }
            Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
                self.running.store(false, Ordering::Relaxed);
            }
            Event::WindowEvent { event: WindowEvent::Resized(PhysicalSize { width, height }), .. } => {
                self.window.on_resize(width, height);
                self.ui_host.resize(self.window.width(), self.window.height());
            }
            Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, .. } => {
                self.ui_host.on_key(input.state, input.virtual_keycode, input.scancode, input.modifiers);
            }
            Event::WindowEvent { event: WindowEvent::ReceivedCharacter(c), .. } => {
                self.ui_host.on_key_typed(c);
            }
            Event::WindowEvent { event: WindowEvent::Focused(state), .. } => {
//          if !gui_mode {
//            gl_window.set_cursor_grab(state).unwrap();
//            gl_window.set_cursor_visible(!state);
//            gl_window.set_cursor_position(center).unwrap();
//          }
      }
      Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => {
            }
            Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => {
//          mdiff = Some((position.x - center.x, position.y - center.y));
        self.ui_host.on_mouse_move(&DrawPreview::from(self.fr.choose((FONT_SIZE, false, self.window.dpi))),
                                   position.x as i32, position.y as i32);
      }
      Event::WindowEvent { event: WindowEvent::MouseInput { device_id, state, button, modifiers }, .. } => {
        self.ui_host.on_click(&DrawPreview::from(self.fr.choose((FONT_SIZE, false, self.window.dpi))),
                              state, button, modifiers)
      }
      Event::WindowEvent { event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, .. } => {
        self.window.on_dpi_changed(scale_factor);
        // self.ui_host.resize(self.window.width(), self.window.height());
      }
      _ => ()
    }
                self.ui_host.on_mouse_move(&DrawPreview::from(self.fr.choose((FONT_SIZE, false, self.window.dpi))),
                                           position.x as i32, position.y as i32);
            }
            Event::WindowEvent { event: WindowEvent::MouseInput { device_id, state, button, modifiers }, .. } => {
                self.ui_host.on_click(&DrawPreview::from(self.fr.choose((FONT_SIZE, false, self.window.dpi))),
                                      state, button, modifiers)
            }
            Event::WindowEvent { event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, .. } => {
                self.window.on_dpi_changed(scale_factor);
                // self.ui_host.resize(self.window.width(), self.window.height());
            }
            _ => ()
        }

    if !self.running.load(Ordering::Relaxed) {
      *cf = ControlFlow::Exit;
        if !self.running.load(Ordering::Relaxed) {
            *cf = ControlFlow::Exit;
        }
    }
  }

  fn cycle(&mut self, limit: bool) {
    self.fps.end_frame(if limit { self.ce.get_cvar("fps_limit").unwrap().get_int_value().unwrap() as u64 } else { 0 });
    self.draw();
    fn cycle(&mut self, limit: bool) {
        self.fps.end_frame(if limit { self.ce.get_cvar("fps_limit").unwrap().get_int_value().unwrap() as u64 } else { 0 });
        self.draw();

//    let center = LogicalPosition::new(self.window.scaled_width as f64 / 2.0, self.window.scaled_height as f64 / 2.0);
//    let mut mdiff = None;


@@ 363,267 363,267 @@ impl Game<'_> {
//      camera.shift(cam_mat * movement * 0.1);
//    }

    self.ce.dispatcher_mut().resume();
  }

  fn draw(&mut self) {
    let Game {
      env,
      ce,
      running,
      sm,
      window,
      ui_host,
      kb,
      camera,
      display,
      fps,
      fr,
      ui_settings,
    } = self;

    let display = *display;

    let time_start = Instant::now();
    let mut frame = display.draw();
    frame.clear(None, Some((0.0, 0.0, 0.0, 0.0)), false, Some(1.0), None);
    let mut props = RenderProperties::new(&camera.borrow(), 90.0, window.width(), window.height());

    let gui_mat = Mat4::get_ortho(0.0, window.width() as f32, window.height() as f32, 0.0, -1000.0, 1000.0);

    let mut surface = AnySurface::Frame(&mut frame);
    let mut g = Graphics::new(display, &mut surface, self.fr.choose((FONT_SIZE, false, window.dpi)), &mut self.sm, gui_mat, ui_settings.get());

    self.ui_host.draw(&mut g);

    let fr = self.fr.choose((14, false, window.dpi));

    let color = Color::white();
    if self.ce.get_cvar("debug_info").unwrap().get_bool_value() {
      g.push_matrix(|g| {
        g.translatei(0, 0, 100);
        g.draw_string(&format!("fps: {} ({} actual)", fps.smooth_fps(), fps.current_fps()), 2, 2, StringDrawProps::default());

        let camera = camera.borrow();
        g.draw_string(&format!("pos: {:5.3} {:5.3} {:5.3}",
                               PrettyPrintFloat(camera.pos().x() as f64),
                               PrettyPrintFloat(camera.pos().y() as f64),
                               PrettyPrintFloat(camera.pos().z() as f64)),
                      2, 2 + fr.get_height() as i32, StringDrawProps::default());
        g.draw_string(&format!("ang: {:5.3} {:5.3} {:5.3}",
                               PrettyPrintFloat(camera.yaw() as f64),
                               PrettyPrintFloat(camera.pitch() as f64),
                               PrettyPrintFloat(camera.roll() as f64)),
                      2, 2 + 2 * fr.get_height() as i32, StringDrawProps::default());
      });
        self.ce.dispatcher_mut().resume();
    }
    drop(g);
    frame.finish().unwrap();
  }

  fn mouse_move(&mut self) {}
    fn draw(&mut self) {
        let Game {
            env,
            ce,
            running,
            sm,
            window,
            ui_host,
            kb,
            camera,
            display,
            fps,
            fr,
            ui_settings,
        } = self;

        let display = *display;

        let time_start = Instant::now();
        let mut frame = display.draw();
        frame.clear(None, Some((0.0, 0.0, 0.0, 0.0)), false, Some(1.0), None);
        let mut props = RenderProperties::new(&camera.borrow(), 90.0, window.width(), window.height());

        let gui_mat = Mat4::get_ortho(0.0, window.width() as f32, window.height() as f32, 0.0, -1000.0, 1000.0);

        let mut surface = AnySurface::Frame(&mut frame);
        let mut g = Graphics::new(display, &mut surface, self.fr.choose((FONT_SIZE, false, window.dpi)), &mut self.sm, gui_mat, ui_settings.get());

        self.ui_host.draw(&mut g);

        let fr = self.fr.choose((14, false, window.dpi));

        let color = Color::white();
        if self.ce.get_cvar("debug_info").unwrap().get_bool_value() {
            g.push_matrix(|g| {
                g.translatei(0, 0, 100);
                g.draw_string(&format!("fps: {} ({} actual)", fps.smooth_fps(), fps.current_fps()), 2, 2, StringDrawProps::default());

                let camera = camera.borrow();
                g.draw_string(&format!("pos: {:5.3} {:5.3} {:5.3}",
                                       PrettyPrintFloat(camera.pos().x() as f64),
                                       PrettyPrintFloat(camera.pos().y() as f64),
                                       PrettyPrintFloat(camera.pos().z() as f64)),
                              2, 2 + fr.get_height() as i32, StringDrawProps::default());
                g.draw_string(&format!("ang: {:5.3} {:5.3} {:5.3}",
                                       PrettyPrintFloat(camera.yaw() as f64),
                                       PrettyPrintFloat(camera.pitch() as f64),
                                       PrettyPrintFloat(camera.roll() as f64)),
                              2, 2 + 2 * fr.get_height() as i32, StringDrawProps::default());
            });
        }
        drop(g);
        frame.finish().unwrap();
    }

    fn mouse_move(&mut self) {}
}

pub struct FpsCounter {
  last_frame_start: Instant,
  current_fps: u64,
  smooth_fps: u64,
  last_smooth_fps: Instant,
  frames_since_last: u64,
    last_frame_start: Instant,
    current_fps: u64,
    smooth_fps: u64,
    last_smooth_fps: Instant,
    frames_since_last: u64,
}

impl FpsCounter {
  pub fn new() -> Self {
    FpsCounter {
      last_frame_start: Instant::now(),
      current_fps: 0,
      smooth_fps: 0,
      last_smooth_fps: Instant::now(),
      frames_since_last: 0,
    }
  }

  pub fn end_frame(&mut self, fps_target: u64) {
    if fps_target > 0 {
      let frame_time = Duration::from_micros(1000000 / fps_target);
      let elapsed = self.last_frame_start.elapsed();
      if elapsed < frame_time {
        sleep(frame_time.clone().sub(elapsed));
      }
    pub fn new() -> Self {
        FpsCounter {
            last_frame_start: Instant::now(),
            current_fps: 0,
            smooth_fps: 0,
            last_smooth_fps: Instant::now(),
            frames_since_last: 0,
        }
    }

    self.current_fps = (1000000000 / self.last_frame_start.elapsed().as_nanos()) as u64;
    self.frames_since_last += 1;
    let last_smooth = self.last_smooth_fps.elapsed();
    if last_smooth.as_secs() > 0 {
      self.smooth_fps = self.frames_since_last * 1000000000 / last_smooth.as_nanos() as u64;
      self.frames_since_last = 0;
      self.last_smooth_fps = Instant::now();
    }
    pub fn end_frame(&mut self, fps_target: u64) {
        if fps_target > 0 {
            let frame_time = Duration::from_micros(1000000 / fps_target);
            let elapsed = self.last_frame_start.elapsed();
            if elapsed < frame_time {
                sleep(frame_time.clone().sub(elapsed));
            }
        }

        self.current_fps = (1000000000 / self.last_frame_start.elapsed().as_nanos()) as u64;
        self.frames_since_last += 1;
        let last_smooth = self.last_smooth_fps.elapsed();
        if last_smooth.as_secs() > 0 {
            self.smooth_fps = self.frames_since_last * 1000000000 / last_smooth.as_nanos() as u64;
            self.frames_since_last = 0;
            self.last_smooth_fps = Instant::now();
        }

    self.last_frame_start = Instant::now();
  }
        self.last_frame_start = Instant::now();
    }

  pub fn smooth_fps(&self) -> u64 { self.smooth_fps }
    pub fn smooth_fps(&self) -> u64 { self.smooth_fps }

  pub fn current_fps(&self) -> u64 { self.current_fps }
    pub fn current_fps(&self) -> u64 { self.current_fps }
}

fn draw(surface: &mut AnySurface, model: &Model<Vertex>, props: &RenderProperties) {
  surface.clear_color_and_depth((0.01, 0.01, 0.01, 1.0), 1.0);
    surface.clear_color_and_depth((0.01, 0.01, 0.01, 1.0), 1.0);

  model.draw(surface, props)
    .expect("Failed to draw triangle!");
    model.draw(surface, props)
        .expect("Failed to draw triangle!");
}

fn get_test_model(sm: &mut ShaderManager, display: &Display) -> Model<Vertex> {
  let prog = sm.get("shader/tri.sdef").unwrap();

  let vb = VertexBuffer::immutable(display, &[
    Vertex { pos: [1.0, 1.0, 0.0], normal: [0.0, 0.0, -1.0] },
    Vertex { pos: [1.0, 0.0, 0.0], normal: [0.0, 0.0, -1.0] },
    Vertex { pos: [0.0, 0.0, 0.0], normal: [0.0, 0.0, -1.0] },
    Vertex { pos: [0.0, 1.0, 0.0], normal: [0.0, 0.0, -1.0] },
    Vertex { pos: [1.0, 1.0, 0.0], normal: [0.0, 0.0, -1.0] },
    Vertex { pos: [0.0, 0.0, 0.0], normal: [0.0, 0.0, -1.0] },
    Vertex { pos: [0.0, 0.0, 0.0], normal: [0.0, -1.0, 0.0] },
    Vertex { pos: [1.0, 0.0, 0.0], normal: [0.0, -1.0, 0.0] },
    Vertex { pos: [1.0, 0.0, 1.0], normal: [0.0, -1.0, 0.0] },
    Vertex { pos: [0.0, 0.0, 0.0], normal: [0.0, -1.0, 0.0] },
    Vertex { pos: [1.0, 0.0, 1.0], normal: [0.0, -1.0, 0.0] },
    Vertex { pos: [0.0, 0.0, 1.0], normal: [0.0, -1.0, 0.0] },
    Vertex { pos: [0.0, 1.0, 1.0], normal: [-1.0, 0.0, 0.0] },
    Vertex { pos: [0.0, 1.0, 0.0], normal: [-1.0, 0.0, 0.0] },
    Vertex { pos: [0.0, 0.0, 0.0], normal: [-1.0, 0.0, 0.0] },
    Vertex { pos: [0.0, 0.0, 1.0], normal: [-1.0, 0.0, 0.0] },
    Vertex { pos: [0.0, 1.0, 1.0], normal: [-1.0, 0.0, 0.0] },
    Vertex { pos: [0.0, 0.0, 0.0], normal: [-1.0, 0.0, 0.0] },
    Vertex { pos: [0.0, 0.0, 1.0], normal: [0.0, 0.0, 1.0] },
    Vertex { pos: [1.0, 0.0, 1.0], normal: [0.0, 0.0, 1.0] },
    Vertex { pos: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
    Vertex { pos: [0.0, 0.0, 1.0], normal: [0.0, 0.0, 1.0] },
    Vertex { pos: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
    Vertex { pos: [0.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
    Vertex { pos: [1.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0] },
    Vertex { pos: [1.0, 1.0, 0.0], normal: [0.0, 1.0, 0.0] },
    Vertex { pos: [0.0, 1.0, 0.0], normal: [0.0, 1.0, 0.0] },
    Vertex { pos: [0.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0] },
    Vertex { pos: [1.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0] },
    Vertex { pos: [0.0, 1.0, 0.0], normal: [0.0, 1.0, 0.0] },
    Vertex { pos: [1.0, 0.0, 0.0], normal: [1.0, 0.0, 0.0] },
    Vertex { pos: [1.0, 1.0, 0.0], normal: [1.0, 0.0, 0.0] },
    Vertex { pos: [1.0, 1.0, 1.0], normal: [1.0, 0.0, 0.0] },
    Vertex { pos: [1.0, 0.0, 0.0], normal: [1.0, 0.0, 0.0] },
    Vertex { pos: [1.0, 1.0, 1.0], normal: [1.0, 0.0, 0.0] },
    Vertex { pos: [1.0, 0.0, 1.0], normal: [1.0, 0.0, 0.0] },
  ]).unwrap();

  Model::of(prog, vb)
    let prog = sm.get("shader/tri.sdef").unwrap();

    let vb = VertexBuffer::immutable(display, &[
        Vertex { pos: [1.0, 1.0, 0.0], normal: [0.0, 0.0, -1.0] },
        Vertex { pos: [1.0, 0.0, 0.0], normal: [0.0, 0.0, -1.0] },
        Vertex { pos: [0.0, 0.0, 0.0], normal: [0.0, 0.0, -1.0] },
        Vertex { pos: [0.0, 1.0, 0.0], normal: [0.0, 0.0, -1.0] },
        Vertex { pos: [1.0, 1.0, 0.0], normal: [0.0, 0.0, -1.0] },
        Vertex { pos: [0.0, 0.0, 0.0], normal: [0.0, 0.0, -1.0] },
        Vertex { pos: [0.0, 0.0, 0.0], normal: [0.0, -1.0, 0.0] },
        Vertex { pos: [1.0, 0.0, 0.0], normal: [0.0, -1.0, 0.0] },
        Vertex { pos: [1.0, 0.0, 1.0], normal: [0.0, -1.0, 0.0] },
        Vertex { pos: [0.0, 0.0, 0.0], normal: [0.0, -1.0, 0.0] },
        Vertex { pos: [1.0, 0.0, 1.0], normal: [0.0, -1.0, 0.0] },
        Vertex { pos: [0.0, 0.0, 1.0], normal: [0.0, -1.0, 0.0] },
        Vertex { pos: [0.0, 1.0, 1.0], normal: [-1.0, 0.0, 0.0] },
        Vertex { pos: [0.0, 1.0, 0.0], normal: [-1.0, 0.0, 0.0] },
        Vertex { pos: [0.0, 0.0, 0.0], normal: [-1.0, 0.0, 0.0] },
        Vertex { pos: [0.0, 0.0, 1.0], normal: [-1.0, 0.0, 0.0] },
        Vertex { pos: [0.0, 1.0, 1.0], normal: [-1.0, 0.0, 0.0] },
        Vertex { pos: [0.0, 0.0, 0.0], normal: [-1.0, 0.0, 0.0] },
        Vertex { pos: [0.0, 0.0, 1.0], normal: [0.0, 0.0, 1.0] },
        Vertex { pos: [1.0, 0.0, 1.0], normal: [0.0, 0.0, 1.0] },
        Vertex { pos: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
        Vertex { pos: [0.0, 0.0, 1.0], normal: [0.0, 0.0, 1.0] },
        Vertex { pos: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
        Vertex { pos: [0.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
        Vertex { pos: [1.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0] },
        Vertex { pos: [1.0, 1.0, 0.0], normal: [0.0, 1.0, 0.0] },
        Vertex { pos: [0.0, 1.0, 0.0], normal: [0.0, 1.0, 0.0] },
        Vertex { pos: [0.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0] },
        Vertex { pos: [1.0, 1.0, 1.0], normal: [0.0, 1.0, 0.0] },
        Vertex { pos: [0.0, 1.0, 0.0], normal: [0.0, 1.0, 0.0] },
        Vertex { pos: [1.0, 0.0, 0.0], normal: [1.0, 0.0, 0.0] },
        Vertex { pos: [1.0, 1.0, 0.0], normal: [1.0, 0.0, 0.0] },
        Vertex { pos: [1.0, 1.0, 1.0], normal: [1.0, 0.0, 0.0] },
        Vertex { pos: [1.0, 0.0, 0.0], normal: [1.0, 0.0, 0.0] },
        Vertex { pos: [1.0, 1.0, 1.0], normal: [1.0, 0.0, 0.0] },
        Vertex { pos: [1.0, 0.0, 1.0], normal: [1.0, 0.0, 0.0] },
    ]).unwrap();

    Model::of(prog, vb)
}

struct SelImpl;

impl Selector<(i32, bool, f64), (i32, bool)> for SelImpl {
  fn select(&self, (r_size, r_bold, dpi): &(i32, bool, f64), (f_size, f_bold): &(i32, bool)) -> i32 {
    ((*r_size as f64 * dpi) as i32 - f_size).abs() * -10 +
      if r_bold == f_bold { 5 } else { 0 }
  }
    fn select(&self, (r_size, r_bold, dpi): &(i32, bool, f64), (f_size, f_bold): &(i32, bool)) -> i32 {
        ((*r_size as f64 * dpi) as i32 - f_size).abs() * -10 +
            if r_bold == f_bold { 5 } else { 0 }
    }
}

fn load_fonts<'a>(rl: &ResourceLoader, facade: &'a Display, sm: &mut ShaderManager) -> Multifont<(i32, bool, f64), (i32, bool), BakedFont<'a>, SelImpl> {
  let mut fonts = vec![];
  for &i in [12, 14, 16, 18, 20, 22, 24, 28, 32].iter() {
    for &b in [true, false].iter() {
      let mut source = String::new();
      rl.open(format!("font/terminus/ter-u{}{}.bdf", i, if b { "b" } else { "n" })).unwrap().read_to_string(&mut source).unwrap();
      let bdf = bdf::parse(&source.lines().collect::<Vec<_>>()).unwrap();

      let fr = BakedFont::new(facade, bdf, sm.get("shader/font.sdef").unwrap());
      fonts.push(((i, b), fr));
    let mut fonts = vec![];
    for &i in [12, 14, 16, 18, 20, 22, 24, 28, 32].iter() {
        for &b in [true, false].iter() {
            let mut source = String::new();
            rl.open(format!("font/terminus/ter-u{}{}.bdf", i, if b { "b" } else { "n" })).unwrap().read_to_string(&mut source).unwrap();
            let bdf = bdf::parse(&source.lines().collect::<Vec<_>>()).unwrap();

            let fr = BakedFont::new(facade, bdf, sm.get("shader/font.sdef").unwrap());
            fonts.push(((i, b), fr));
        }
    }
  }

  Multifont::new(fonts, SelImpl)
    Multifont::new(fonts, SelImpl)
}

implement_vertex!(Vertex, pos, normal);

#[derive(Copy, Clone)]
pub struct Vertex {
  pos: [f32; 3],
  normal: [f32; 3],
    pos: [f32; 3],
    normal: [f32; 3],
}

struct Window {
  width: u32,
  height: u32,
  dpi: f64,
    width: u32,
    height: u32,
    dpi: f64,
}

impl Window {
  pub fn on_resize(&mut self, width: u32, height: u32) {
    self.width = width;
    self.height = height;
  }
    pub fn on_resize(&mut self, width: u32, height: u32) {
        self.width = width;
        self.height = height;
    }

  pub fn on_dpi_changed(&mut self, f: f64) {
    self.dpi = f;
  }
    pub fn on_dpi_changed(&mut self, f: f64) {
        self.dpi = f;
    }

  pub fn width(&self) -> u32 { self.width }
    pub fn width(&self) -> u32 { self.width }

  pub fn height(&self) -> u32 { self.height }
    pub fn height(&self) -> u32 { self.height }
}

pub struct Model<T: Copy> {
  shader: Rc<Program>,
  buf: VertexBuffer<T>,
    shader: Rc<Program>,
    buf: VertexBuffer<T>,
}

impl<T: Copy> Model<T> {
  pub fn of(shader: Rc<Program>, buf: VertexBuffer<T>) -> Self {
    Model { shader, buf }
  }

  pub fn draw(&self, surface: &mut impl Surface, props: &RenderProperties) -> Result<(), DrawError> {
    let mut dp = DrawParameters::default();
    dp.backface_culling = BackfaceCullingMode::CullClockwise;
    dp.depth.write = true;
    dp.depth.test = DepthTest::IfLess;

    surface.draw(
      &self.buf,
      NoIndices { primitives: PrimitiveType::TrianglesList },
      &self.shader,
      &UniformsStorage::new("mvp", *props.mvp.as_ref()),
      &dp,
    )
  }
    pub fn of(shader: Rc<Program>, buf: VertexBuffer<T>) -> Self {
        Model { shader, buf }
    }

    pub fn draw(&self, surface: &mut impl Surface, props: &RenderProperties) -> Result<(), DrawError> {
        let mut dp = DrawParameters::default();
        dp.backface_culling = BackfaceCullingMode::CullClockwise;
        dp.depth.write = true;
        dp.depth.test = DepthTest::IfLess;

        surface.draw(
            &self.buf,
            NoIndices { primitives: PrimitiveType::TrianglesList },
            &self.shader,
            &UniformsStorage::new("mvp", *props.mvp.as_ref()),
            &dp,
        )
    }
}

pub struct RenderProperties {
  p: Mat4,
  mv: Mat4,
  mvp: Mat4,
    p: Mat4,
    mv: Mat4,
    mvp: Mat4,
}

impl RenderProperties {
  pub fn new(camera: &Camera, fov: f32, width: u32, height: u32) -> Self {
    let p = Mat4::get_perspective(fov, width as f32 / height as f32, 0.1, 1000.0);
    let mv = camera.get_matrix();
    RenderProperties {
      p,
      mv,
      mvp: p * mv,
    pub fn new(camera: &Camera, fov: f32, width: u32, height: u32) -> Self {
        let p = Mat4::get_perspective(fov, width as f32 / height as f32, 0.1, 1000.0);
        let mv = camera.get_matrix();
        RenderProperties {
            p,
            mv,
            mvp: p * mv,
        }
    }
  }
}

struct Environment {
  rl: ResourceLoader,
  i18n: I18n,
  data_dir: Vec<PathBuf>,
  save_dir: PathBuf,
  v_sync: bool,
  init_script: PathBuf,
    rl: ResourceLoader,
    i18n: I18n,
    data_dir: Vec<PathBuf>,
    save_dir: PathBuf,
    v_sync: bool,
    init_script: PathBuf,
}
\ No newline at end of file

M src/math/mat3.rs => src/math/mat3.rs +55 -55
@@ 8,74 8,74 @@ use crate::math::{Mat4, Vec3};
pub struct Mat3(Matrix3<f32>);

impl Mat3 {
  pub fn identity() -> Self {
    Mat3(Matrix3::identity())
  }

  pub fn from(data: &[f32; 9]) -> Self {
    Mat3(Matrix3::from_row_slice(data))
  }

  pub fn rotate(&self, axis: Vec3, angle: f32) -> Mat3 {
    *self * Mat3::get_rotation(axis, angle)
  }

  pub fn scale(&self, scale: Vec3) -> Mat3 {
    *self * Mat3::get_scale(scale)
  }

  pub fn into_mat4(self) -> Mat4 {
    let mut arr = [0.0; 16];
    arr[15] = 1.0;
    for (y, a) in self.as_ref().iter().enumerate() {
      for (x, data) in a.iter().enumerate() {
        arr[x + y * 4] = *data;
      }
    pub fn identity() -> Self {
        Mat3(Matrix3::identity())
    }

    pub fn from(data: &[f32; 9]) -> Self {
        Mat3(Matrix3::from_row_slice(data))
    }

    pub fn rotate(&self, axis: Vec3, angle: f32) -> Mat3 {
        *self * Mat3::get_rotation(axis, angle)
    }

    pub fn scale(&self, scale: Vec3) -> Mat3 {
        *self * Mat3::get_scale(scale)
    }

    pub fn into_mat4(self) -> Mat4 {
        let mut arr = [0.0; 16];
        arr[15] = 1.0;
        for (y, a) in self.as_ref().iter().enumerate() {
            for (x, data) in a.iter().enumerate() {
                arr[x + y * 4] = *data;
            }
        }
        Mat4::from(&arr)
    }
    Mat4::from(&arr)
  }
}

impl AsRef<[[f32; 3]; 3]> for Mat3 {
  fn as_ref(&self) -> &[[f32; 3]; 3] {
    self.0.as_ref()
  }
    fn as_ref(&self) -> &[[f32; 3]; 3] {
        self.0.as_ref()
    }
}

impl Mul for Mat3 {
  type Output = Mat3;
    type Output = Mat3;

  fn mul(self, rhs: Self) -> Self::Output {
    Mat3(self.0 * rhs.0)
  }
    fn mul(self, rhs: Self) -> Self::Output {
        Mat3(self.0 * rhs.0)
    }
}

impl Mul<Vec3> for Mat3 {
  type Output = Vec3;
    type Output = Vec3;

  fn mul(self, rhs: Vec3) -> Self::Output {
    (self.0 * rhs.inner()).into()
  }
    fn mul(self, rhs: Vec3) -> Self::Output {
        (self.0 * rhs.inner()).into()
    }
}

impl Mat3 {
  pub fn get_rotation(axis: Vec3, angle: f32) -> Self {
    let c = -angle.to_radians().cos();
    let s = -angle.to_radians().sin();
    let t = 1.0 - c;

    Mat3::from(&[
      t * axis.x() * axis.x() + c, t * axis.x() * axis.y() - s * axis.z(), t * axis.x() * axis.z() + s * axis.y(),
      t * axis.x() * axis.y() + s * axis.z(), t * axis.y() * axis.y() + c, t * axis.y() * axis.z() - s * axis.x(),
      t * axis.x() * axis.z() - s * axis.y(), t * axis.y() * axis.z() + s * axis.x(), t * axis.z() * axis.z() + c,
    ])
  }

  pub fn get_scale(scale: Vec3) -> Self {
    Mat3::from(&[
      scale.x(), 0.0, 0.0,
      0.0, scale.y(), 0.0,
      0.0, 0.0, scale.z(),
    ])
  }
    pub fn get_rotation(axis: Vec3, angle: f32) -> Self {
        let c = -angle.to_radians().cos();
        let s = -angle.to_radians().sin();
        let t = 1.0 - c;

        Mat3::from(&[
            t * axis.x() * axis.x() + c, t * axis.x() * axis.y() - s * axis.z(), t * axis.x() * axis.z() + s * axis.y(),
            t * axis.x() * axis.y() + s * axis.z(), t * axis.y() * axis.y() + c, t * axis.y() * axis.z() - s * axis.x(),
            t * axis.x() * axis.z() - s * axis.y(), t * axis.y() * axis.z() + s * axis.x(), t * axis.z() * axis.z() + c,
        ])
    }

    pub fn get_scale(scale: Vec3) -> Self {
        Mat3::from(&[
            scale.x(), 0.0, 0.0,
            0.0, scale.y(), 0.0,
            0.0, 0.0, scale.z(),
        ])
    }
}
\ No newline at end of file

M src/math/mat4.rs => src/math/mat4.rs +86 -86
@@ 9,111 9,111 @@ use crate::math::Vec4;
pub struct Mat4(Matrix4<f32>);

impl Mat4 {
  pub fn identity() -> Self {
    Mat4(Matrix4::identity())
  }

  pub fn from(data: &[f32; 16]) -> Self {
    Mat4(Matrix4::from_row_slice(data))
  }

  pub fn translate(&self, offset: Vec3) -> Mat4 {
    *self * Mat4::get_translation(offset)
  }

  pub fn rotate(&self, axis: Vec3, angle: f32) -> Mat4 {
    *self * Mat4::get_rotation(axis, angle)
  }

  pub fn scale(&self, scale: Vec3) -> Mat4 {
    *self * Mat4::get_scale(scale)
  }

  pub fn into_mat3(self) -> Mat3 {
    let mut dest = [0.0; 9];
    self.as_ref().into_iter()
      .flat_map(|x| x[0..3].into_iter())
      .zip(dest.iter_mut())
      .for_each(|(src, dest)| *dest = *src);
    Mat3::from(&dest)
  }
    pub fn identity() -> Self {
        Mat4(Matrix4::identity())
    }

    pub fn from(data: &[f32; 16]) -> Self {
        Mat4(Matrix4::from_row_slice(data))
    }

    pub fn translate(&self, offset: Vec3) -> Mat4 {
        *self * Mat4::get_translation(offset)
    }

    pub fn rotate(&self, axis: Vec3, angle: f32) -> Mat4 {
        *self * Mat4::get_rotation(axis, angle)
    }

    pub fn scale(&self, scale: Vec3) -> Mat4 {
        *self * Mat4::get_scale(scale)
    }

    pub fn into_mat3(self) -> Mat3 {
        let mut dest = [0.0; 9];
        self.as_ref().into_iter()
            .flat_map(|x| x[0..3].into_iter())
            .zip(dest.iter_mut())
            .for_each(|(src, dest)| *dest = *src);
        Mat3::from(&dest)
    }
}

impl AsRef<[[f32; 4]; 4]> for Mat4 {
  fn as_ref(&self) -> &[[f32; 4]; 4] {
    self.0.as_ref()
  }
    fn as_ref(&self) -> &[[f32; 4]; 4] {
        self.0.as_ref()
    }
}

impl Mul for Mat4 {
  type Output = Mat4;
    type Output = Mat4;

  fn mul(self, rhs: Self) -> Self::Output {
    Mat4(self.0 * rhs.0)
  }
    fn mul(self, rhs: Self) -> Self::Output {
        Mat4(self.0 * rhs.0)
    }
}

impl MulAssign for Mat4 {
  fn mul_assign(&mut self, rhs: Self) {
    *self = *self * rhs;
  }
    fn mul_assign(&mut self, rhs: Self) {
        *self = *self * rhs;
    }
}

impl Mul<Vec3> for Mat4 {
  type Output = Vec3;
    type Output = Vec3;

  fn mul(self, rhs: Vec3) -> Self::Output {
    (self * rhs.into_vec4()).into_vec3()
  }
    fn mul(self, rhs: Vec3) -> Self::Output {
        (self * rhs.into_vec4()).into_vec3()
    }
}

impl Mul<Vec4> for Mat4 {
  type Output = Vec4;
    type Output = Vec4;

  fn mul(self, rhs: Vec4) -> Self::Output {
    (self.0 * rhs.inner()).into()
  }
    fn mul(self, rhs: Vec4) -> Self::Output {
        (self.0 * rhs.inner()).into()
    }
}

impl Mat4 {
  pub fn get_rotation(axis: Vec3, angle: f32) -> Self {
    Mat3::get_rotation(axis, angle).into_mat4()
  }

  pub fn get_translation(offset: Vec3) -> Self {
    Mat4::from(&[
      1.0, 0.0, 0.0, offset.x(),
      0.0, 1.0, 0.0, offset.y(),
      0.0, 0.0, 1.0, offset.z(),
      0.0, 0.0, 0.0, 1.0
    ])
  }

  pub fn get_scale(scale: Vec3) -> Self {
    Mat3::get_scale(scale).into_mat4()
  }

  pub fn get_perspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) -> Self {
    let half_fov_y_radians = (fov_y / 2.0).to_radians();
    let range = half_fov_y_radians.tan() * z_near;
    let left = -range * aspect;
    let right = range * aspect;
    let bottom = -range;

    Mat4::from(&[
      2.0 * z_near / (right - left), 0.0, 0.0, 0.0,
      0.0, 2.0 * z_near / (range - bottom), 0.0, 0.0,
      0.0, 0.0, (-(z_far + z_near) / (z_far - z_near)), -(2.0 * z_far * z_near) / (z_far - z_near),
      0.0, 0.0, -1.0, 0.0
    ])
  }

  pub fn get_ortho(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Self {
    Mat4::from(&[
      2.0 / (right - left), 0.0, 0.0, -((right + left) / (right - left)),
      0.0, 2.0 / (top - bottom), 0.0, -((top + bottom) / (top - bottom)),
      0.0, 0.0, -2.0 / (far - near), -((far + near) / (far - near)),
      0.0, 0.0, 0.0, 1.0
    ])
  }
    pub fn get_rotation(axis: Vec3, angle: f32) -> Self {
        Mat3::get_rotation(axis, angle).into_mat4()
    }

    pub fn get_translation(offset: Vec3) -> Self {
        Mat4::from(&[
            1.0, 0.0, 0.0, offset.x(),
            0.0, 1.0, 0.0, offset.y(),
            0.0, 0.0, 1.0, offset.z(),
            0.0, 0.0, 0.0, 1.0
        ])
    }

    pub fn get_scale(scale: Vec3) -> Self {
        Mat3::get_scale(scale).into_mat4()
    }

    pub fn get_perspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) -> Self {
        let half_fov_y_radians = (fov_y / 2.0).to_radians();
        let range = half_fov_y_radians.tan() * z_near;
        let left = -range * aspect;
        let right = range * aspect;
        let bottom = -range;

        Mat4::from(&[
            2.0 * z_near / (right - left), 0.0, 0.0, 0.0,
            0.0, 2.0 * z_near / (range - bottom), 0.0, 0.0,
            0.0, 0.0, (-(z_far + z_near) / (z_far - z_near)), -(2.0 * z_far * z_near) / (z_far - z_near),
            0.0, 0.0, -1.0, 0.0
        ])
    }

    pub fn get_ortho(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Self {
        Mat4::from(&[
            2.0 / (right - left), 0.0, 0.0, -((right + left) / (right - left)),
            0.0, 2.0 / (top - bottom), 0.0, -((top + bottom) / (top - bottom)),
            0.0, 0.0, -2.0 / (far - near), -((far + near) / (far - near)),
            0.0, 0.0, 0.0, 1.0
        ])
    }
}
\ No newline at end of file

M src/math/vec3.rs => src/math/vec3.rs +62 -62
@@ 6,132 6,132 @@ use nalgebra::Vector3;
use crate::math::Vec4;

pub fn vec3(x: f32, y: f32, z: f32) -> Vec3 {
  Vec3::of(x, y, z)
    Vec3::of(x, y, z)
}

#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Vec3(Vector3<f32>);

impl Vec3 {
  pub fn origin() -> Self {
    Vec3(Vector3::new(0.0, 0.0, 0.0))
  }
    pub fn origin() -> Self {
        Vec3(Vector3::new(0.0, 0.0, 0.0))
    }

  pub fn of(x: f32, y: f32, z: f32) -> Self {
    Vec3(Vector3::new(x, y, z))
  }
    pub fn of(x: f32, y: f32, z: f32) -> Self {
        Vec3(Vector3::new(x, y, z))
    }

  pub fn into_vec4(self) -> Vec4 {
    Vec4::of(self.x(), self.y(), self.z(), 1.0)
  }
    pub fn into_vec4(self) -> Vec4 {
        Vec4::of(self.x(), self.y(), self.z(), 1.0)
    }

  pub fn inner(&self) -> &Vector3<f32> { &self.0 }
    pub fn inner(&self) -> &Vector3<f32> { &self.0 }

  pub fn x(&self) -> f32 { self.0.x }
    pub fn x(&self) -> f32 { self.0.x }

  pub fn y(&self) -> f32 { self.0.y }
    pub fn y(&self) -> f32 { self.0.y }

  pub fn z(&self) -> f32 { self.0.z }
    pub fn z(&self) -> f32 { self.0.z }
}

impl Add for Vec3 {
  type Output = Vec3;
    type Output = Vec3;

  fn add(self, rhs: Self) -> Self::Output {
    Vec3(self.0 + rhs.0)
  }
    fn add(self, rhs: Self) -> Self::Output {
        Vec3(self.0 + rhs.0)
    }
}

impl AddAssign for Vec3 {
  fn add_assign(&mut self, rhs: Self) {
    *self = *self + rhs;
  }
    fn add_assign(&mut self, rhs: Self) {
        *self = *self + rhs;
    }
}

impl Sub for Vec3 {
  type Output = Vec3;
    type Output = Vec3;

  fn sub(self, rhs: Self) -> Self::Output {
    Vec3(self.0 - rhs.0)
  }
    fn sub(self, rhs: Self) -> Self::Output {
        Vec3(self.0 - rhs.0)
    }
}

impl SubAssign for Vec3 {
  fn sub_assign(&mut self, rhs: Self) {
    *self = *self - rhs;
  }
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}

impl Mul<f32> for Vec3 {
  type Output = Vec3;
    type Output = Vec3;

  fn mul(self, rhs: f32) -> Self::Output {
    Vec3(self.0 * rhs)
  }
    fn mul(self, rhs: f32) -> Self::Output {
        Vec3(self.0 * rhs)
    }
}

impl MulAssign<f32> for Vec3 {
  fn mul_assign(&mut self, rhs: f32) {
    *self = *self * rhs;
  }
    fn mul_assign(&mut self, rhs: f32) {
        *self = *self * rhs;
    }
}

impl Mul for Vec3 {
  type Output = Vec3;
    type Output = Vec3;

  fn mul(self, rhs: Self) -> Self::Output {
    Vec3(self.0.component_mul(&rhs.0))
  }
    fn mul(self, rhs: Self) -> Self::Output {
        Vec3(self.0.component_mul(&rhs.0))
    }
}

impl MulAssign for Vec3 {
  fn mul_assign(&mut self, rhs: Self) {
    *self = *self * rhs;
  }
    fn mul_assign(&mut self, rhs: Self) {
        *self = *self * rhs;
    }
}

impl Div<f32> for Vec3 {
  type Output = Vec3;
    type Output = Vec3;

  fn div(self, rhs: f32) -> Self::Output {
    Vec3(self.0 / rhs)
  }
    fn div(self, rhs: f32) -> Self::Output {
        Vec3(self.0 / rhs)
    }
}

impl DivAssign<f32> for Vec3 {
  fn div_assign(&mut self, rhs: f32) {
    *self = *self / rhs;
  }
    fn div_assign(&mut self, rhs: f32) {
        *self = *self / rhs;
    }
}

impl Div for Vec3 {
  type Output = Vec3;
    type Output = Vec3;

  fn div(self, rhs: Self) -> Self::Output {
    Vec3(self.0.component_div(&rhs.0))
  }
    fn div(self, rhs: Self) -> Self::Output {
        Vec3(self.0.component_div(&rhs.0))
    }
}

impl DivAssign for Vec3 {
  fn div_assign(&mut self, rhs: Self) {
    *self = *self / rhs;
  }
    fn div_assign(&mut self, rhs: Self) {
        *self = *self / rhs;
    }
}

impl AsRef<[f32; 3]> for Vec3 {
  fn as_ref(&self) -> &[f32; 3] { self.0.as_ref() }
    fn as_ref(&self) -> &[f32; 3] { self.0.as_ref() }
}

impl Display for Vec3 {
  fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
    write!(f, "({}, {}, {})", self.x(), self.y(), self.z())
  }
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(f, "({}, {}, {})", self.x(), self.y(), self.z())
    }
}

impl From<Vec3> for Vector3<f32> {
  fn from(vec: Vec3) -> Self { vec.0 }
    fn from(vec: Vec3) -> Self { vec.0 }
}

impl From<Vector3<f32>> for Vec3 {
  fn from(vec: Vector3<f32>) -> Self { Vec3(vec) }
    fn from(vec: Vector3<f32>) -> Self { Vec3(vec) }
}
\ No newline at end of file

M src/math/vec4.rs => src/math/vec4.rs +45 -45
@@ 6,98 6,98 @@ use nalgebra::Vector4;
use crate::math::Vec3;

pub fn vec4(x: f32, y: f32, z: f32, w: f32) -> Vec4 {
  Vec4::of(x, y, z, w)
    Vec4::of(x, y, z, w)
}

#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Vec4(Vector4<f32>);

impl Vec4 {
  pub fn origin() -> Self {
    Vec4(Vector4::new(0.0, 0.0, 0.0, 0.0))
  }
    pub fn origin() -> Self {
        Vec4(Vector4::new(0.0, 0.0, 0.0, 0.0))
    }

  pub fn of(x: f32, y: f32, z: f32, w: f32) -> Self {
    Vec4(Vector4::new(x, y, z, w))
  }
    pub fn of(x: f32, y: f32, z: f32, w: f32) -> Self {
        Vec4(Vector4::new(x, y, z, w))
    }

  pub fn into_vec3(self) -> Vec3 {
    Vec3::of(self.x() / self.w(), self.y() / self.w(), self.z() / self.w())
  }
    pub fn into_vec3(self) -> Vec3 {
        Vec3::of(self.x() / self.w(), self.y() / self.w(), self.z() / self.w())
    }

  pub fn inner(&self) -> &Vector4<f32> { &self.0 }
    pub fn inner(&self) -> &Vector4<f32> { &self.0 }

  pub fn x(&self) -> f32 { self.0.x }
    pub fn x(&self) -> f32 { self.0.x }

  pub fn y(&self) -> f32 { self.0.y }
    pub fn y(&self) -> f32 { self.0.y }

  pub fn z(&self) -> f32 { self.0.z }
    pub fn z(&self) -> f32 { self.0.z }

  pub fn w(&self) -> f32 { self.0.w }
    pub fn w(&self) -> f32 { self.0.w }
}

impl Add for Vec4 {
  type Output = Vec4;
    type Output = Vec4;

  fn add(self, rhs: Self) -> Self::Output {
    Vec4(self.0 + rhs.0)
  }
    fn add(self, rhs: Self) -> Self::Output {
        Vec4(self.0 + rhs.0)
    }
}

impl Sub for Vec4 {
  type Output = Vec4;
    type Output = Vec4;

  fn sub(self, rhs: Self) -> Self::Output {
    Vec4(self.0 - rhs.0)
  }
    fn sub(self, rhs: Self) -> Self::Output {
        Vec4(self.0 - rhs.0)
    }
}

impl Mul<f32> for Vec4 {
  type Output = Vec4;
    type Output = Vec4;

  fn mul(self, rhs: f32) -> Self::Output {
    Vec4(self.0 * rhs)
  }
    fn mul(self, rhs: f32) -> Self::Output {
        Vec4(self.0 * rhs)
    }
}

impl Mul for Vec4 {
  type Output = Vec4;
    type Output = Vec4;

  fn mul(self, rhs: Self) -> Self::Output {
    Vec4(self.0.component_mul(&rhs.0))
  }
    fn mul(self, rhs: Self) -> Self::Output {
        Vec4(self.0.component_mul(&rhs.0))
    }
}

impl Div<f32> for Vec4 {
  type Output = Vec4;
    type Output = Vec4;

  fn div(self, rhs: f32) -> Self::Output {
    Vec4(self.0 / rhs)
  }
    fn div(self, rhs: f32) -> Self::Output {
        Vec4(self.0 / rhs)
    }
}

impl Div for Vec4 {
  type Output = Vec4;
    type Output = Vec4;

  fn div(self, rhs: Self) -> Self::Output {
    Vec4(self.0.component_div(&rhs.0))
  }
    fn div(self, rhs: Self) -> Self::Output {
        Vec4(self.0.component_div(&rhs.0))
    }
}

impl AsRef<[f32; 4]> for Vec4 {
  fn as_ref(&self) -> &[f32; 4] { self.0.as_ref() }
    fn as_ref(&self) -> &[f32; 4] { self.0.as_ref() }
}

impl Display for Vec4 {
  fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
    write!(f, "({}, {}, {})", self.x(), self.y(), self.z())
  }
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(f, "({}, {}, {})", self.x(), self.y(), self.z())
    }
}

impl From<Vec4> for Vector4<f32> {
  fn from(vec: Vec4) -> Self { vec.0 }
    fn from(vec: Vec4) -> Self { vec.0 }
}

impl From<Vector4<f32>> for Vec4 {
  fn from(vec: Vector4<f32>) -> Self { Vec4(vec) }
    fn from(vec: Vector4<f32>) -> Self { Vec4(vec) }
}
\ No newline at end of file

M src/res/merge.rs => src/res/merge.rs +26 -26
@@ 4,44 4,44 @@ use std::path::{Path, PathBuf};
use crate::res::normalize_path;

pub struct MergeModeTable {
  entries: Vec<(PathBuf, MergeMode)>,
    entries: Vec<(PathBuf, MergeMode)>,
}

impl MergeModeTable {
  pub fn new() -> Self {
    MergeModeTable { entries: vec![] }
  }

  pub fn add_entry(&mut self, path: impl AsRef<Path>, mode: MergeMode) {
    self.entries.insert(0, (normalize_path(path).unwrap(), mode));
  }

  pub fn get_mode(&self, path: impl AsRef<Path>) -> MergeMode {
    let normalized = normalize_path(path).unwrap();
    self.entries.iter()
      .find(|(p, _)| matches(p, &normalized))
      .map_or(MergeMode::Overwrite, |(_, mode)| *mode)
  }
    pub fn new() -> Self {
        MergeModeTable { entries: vec![] }
    }

    pub fn add_entry(&mut self, path: impl AsRef<Path>, mode: MergeMode) {
        self.entries.insert(0, (normalize_path(path).unwrap(), mode));
    }

    pub fn get_mode(&self, path: impl AsRef<Path>) -> MergeMode {
        let normalized = normalize_path(path).unwrap();
        self.entries.iter()
            .find(|(p, _)| matches(p, &normalized))
            .map_or(MergeMode::Overwrite, |(_, mode)| *mode)
    }
}

fn matches(entry: impl AsRef<Path>, to_test: impl AsRef<Path>) -> bool {
  to_test.as_ref().starts_with(entry.as_ref())
    to_test.as_ref().starts_with(entry.as_ref())
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum MergeMode {
  Overwrite,
  Concat,
    Overwrite,
    Concat,
}

impl TryFrom<&str> for MergeMode {
  type Error = String;

  fn try_from(value: &str) -> Result<Self, Self::Error> {
    match value {
      "overwrite" => Ok(MergeMode::Overwrite),
      "concat" => Ok(MergeMode::Concat),
      _ => Err(value.to_owned())
    type Error = String;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "overwrite" => Ok(MergeMode::Overwrite),
            "concat" => Ok(MergeMode::Concat),
            _ => Err(value.to_owned())
        }
    }
  }
}
\ No newline at end of file

M src/res/mod.rs => src/res/mod.rs +79 -79
@@ 14,121 14,121 @@ use crate::res::merge::MergeModeTable;
pub mod merge;

pub struct ResourceLoader {
  mmt: MergeModeTable,
  sources: Vec<DataSource>,
    mmt: MergeModeTable,
    sources: Vec<DataSource>,
}

impl ResourceLoader {
  pub fn new(mmt: MergeModeTable, sources: Vec<DataSource>) -> Self {
    ResourceLoader { mmt, sources }
  }

  pub fn open(&self, path: impl AsRef<Path>) -> Result<DataSourceRead> {
    self.sources.iter().filter_map(|el| match el.open(&path) {
      Err(Error::IoError(e)) if e.kind() == ErrorKind::NotFound => None,
      Err(Error::ZipError(ZipError::FileNotFound)) => None,
      x => Some(x),
    }).next().unwrap_or(Err(Error::NotFound))
  }

  pub fn read_to_string(&self, path: impl AsRef<Path>) -> Result<String> {
    self.open(path).and_then(|mut stream| {
      let mut s = String::new();
      stream.read_to_string(&mut s)?;
      Ok(s)
    })
  }
    pub fn new(mmt: MergeModeTable, sources: Vec<DataSource>) -> Self {
        ResourceLoader { mmt, sources }
    }

    pub fn open(&self, path: impl AsRef<Path>) -> Result<DataSourceRead> {
        self.sources.iter().filter_map(|el| match el.open(&path) {
            Err(Error::IoError(e)) if e.kind() == ErrorKind::NotFound => None,
            Err(Error::ZipError(ZipError::FileNotFound)) => None,
            x => Some(x),
        }).next().unwrap_or(Err(Error::NotFound))
    }

    pub fn read_to_string(&self, path: impl AsRef<Path>) -> Result<String> {
        self.open(path).and_then(|mut stream| {
            let mut s = String::new();
            stream.read_to_string(&mut s)?;
            Ok(s)
        })
    }
}

pub enum DataSource {
  Filesystem(PathBuf),
  Archive(PathBuf),
    Filesystem(PathBuf),
    Archive(PathBuf),
}

impl DataSource {
  pub fn open(&self, path: impl AsRef<Path>) -> Result<DataSourceRead> {
    match self {
      DataSource::Filesystem(base_path) => {
        Ok(DataSourceRead::File(File::open(base_path.join(path.as_ref().strip_prefix("/").unwrap_or(path.as_ref())))?))
      }
      DataSource::Archive(archive_path) => {
        fn create_zip_entry(archive: ZipArchive<File>, path: &str) -> Result<DataSourceRead> {
          // jank!
          let inner = Inner { archive, file: None, _pin: PhantomPinned };
          let mut boxed: Pin<Box<Inner>> = Box::pin(inner);
          unsafe {
            let mut_ref: Pin<&mut Inner> = Pin::as_mut(&mut boxed);
            let inner = Pin::get_unchecked_mut(mut_ref);
            let file = inner.archive.by_name(path)?;
            inner.file = Some(transmute(file));
          }
          Ok(DataSourceRead::ZipEntry(boxed))
    pub fn open(&self, path: impl AsRef<Path>) -> Result<DataSourceRead> {
        match self {
            DataSource::Filesystem(base_path) => {
                Ok(DataSourceRead::File(File::open(base_path.join(path.as_ref().strip_prefix("/").unwrap_or(path.as_ref())))?))
            }
            DataSource::Archive(archive_path) => {
                fn create_zip_entry(archive: ZipArchive<File>, path: &str) -> Result<DataSourceRead> {
                    // jank!
                    let inner = Inner { archive, file: None, _pin: PhantomPinned };
                    let mut boxed: Pin<Box<Inner>> = Box::pin(inner);
                    unsafe {
                        let mut_ref: Pin<&mut Inner> = Pin::as_mut(&mut boxed);
                        let inner = Pin::get_unchecked_mut(mut_ref);
                        let file = inner.archive.by_name(path)?;
                        inner.file = Some(transmute(file));
                    }
                    Ok(DataSourceRead::ZipEntry(boxed))
                }

                let archive = ZipArchive::new(File::open(archive_path)?)?;
                let path = Self::resolve_path_for_archive(&path).ok_or_else(|| Error::PathError(path.as_ref().to_path_buf()))?;
                let r = create_zip_entry(archive, &path);
                r
            }
        }

        let archive = ZipArchive::new(File::open(archive_path)?)?;
        let path = Self::resolve_path_for_archive(&path).ok_or_else(|| Error::PathError(path.as_ref().to_path_buf()))?;
        let r = create_zip_entry(archive, &path);
        r
      }
    }
  }

  fn resolve_path_for_archive(path: impl AsRef<Path>) -> Option<String> {
    let pb = normalize_path(path)?;
    Some(pb.strip_prefix("/").unwrap().to_string_lossy().into_owned())
  }
    fn resolve_path_for_archive(path: impl AsRef<Path>) -> Option<String> {
        let pb = normalize_path(path)?;
        Some(pb.strip_prefix("/").unwrap().to_string_lossy().into_owned())
    }
}

pub fn normalize_path(path: impl AsRef<Path>) -> Option<PathBuf> {
  let mut pb = PathBuf::from("/");
  for c in path.as_ref().components() {
    match c {
      Component::Prefix(_) => return None,
      Component::RootDir => {}
      Component::CurDir => {}
      Component::ParentDir => {
        pb.pop();
      }
      Component::Normal(s) => pb.push(s),
    let mut pb = PathBuf::from("/");
    for c in path.as_ref().components() {
        match c {
            Component::Prefix(_) => return None,
            Component::RootDir => {}
            Component::CurDir => {}
            Component::ParentDir => {
                pb.pop();
            }
            Component::Normal(s) => pb.push(s),
        }
    }
  }
  Some(pb)
    Some(pb)
}

pub enum DataSourceRead {
  File(File),
  ZipEntry(Pin<Box<Inner>>),
    File(File),
    ZipEntry(Pin<Box<Inner>>),
}

pub struct Inner {
  archive: ZipArchive<File>,
  file: Option<ZipFile<'static>>,
  _pin: PhantomPinned,
    archive: ZipArchive<File>,
    file: Option<ZipFile<'static>>,
    _pin: PhantomPinned,
}

impl Read for DataSourceRead {
  fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
    match self {
      DataSourceRead::File(f) => f.read(buf),
      DataSourceRead::ZipEntry(e) => unsafe { e.as_mut().get_unchecked_mut().file.as_mut().unwrap().read(buf) },
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        match self {
            DataSourceRead::File(f) => f.read(buf),
            DataSourceRead::ZipEntry(e) => unsafe { e.as_mut().get_unchecked_mut().file.as_mut().unwrap().read(buf) },
        }
    }
  }
}

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
pub enum Error {
  IoError(std::io::Error),
  ZipError(zip::result::ZipError),
  PathError(PathBuf),
  NotFound,
    IoError(std::io::Error),
    ZipError(zip::result::ZipError),
    PathError(PathBuf),
    NotFound,
}

impl From<std::io::Error> for Error {
  fn from(that: std::io::Error) -> Self { Error::IoError(that) }
    fn from(that: std::io::Error) -> Self { Error::IoError(that) }
}

impl From<ZipError> for Error {
  fn from(that: ZipError) -> Self { Error::ZipError(that) }
    fn from(that: ZipError) -> Self { Error::ZipError(that) }
}

M src/shader.rs => src/shader.rs +45 -45
@@ 11,67 11,67 @@ use crate::res;
use crate::res::ResourceLoader;

pub struct ShaderManager<'a> {
  facade: &'a dyn Facade,
  rl: &'a ResourceLoader,
  programs: HashMap<PathBuf, Result<Rc<Program>, Error>>,
    facade: &'a dyn Facade,
    rl: &'a ResourceLoader,
    programs: HashMap<PathBuf, Result<Rc<Program>, Error>>,
}

impl<'a> ShaderManager<'a> {
  pub fn new(facade: &'a dyn Facade, rl: &'a ResourceLoader) -> Self {
    ShaderManager {
      facade,
      rl,
      programs: Default::default(),
    pub fn new(facade: &'a dyn Facade, rl: &'a ResourceLoader) -> Self {
        ShaderManager {
            facade,
            rl,
            programs: Default::default(),
        }
    }
  }

  pub fn get(&mut self, path: impl AsRef<Path>) -> Result<Rc<Program>, &Error> {
    let facade = self.facade;
    let rl = self.rl;
    self.programs.entry(path.as_ref().to_path_buf()).or_insert_with(|| Self::do_load(facade, rl, path).map(Rc::new)).as_ref().map(|a| a.clone())
  }
    pub fn get(&mut self, path: impl AsRef<Path>) -> Result<Rc<Program>, &Error> {
        let facade = self.facade;
        let rl = self.rl;
        self.programs.entry(path.as_ref().to_path_buf()).or_insert_with(|| Self::do_load(facade, rl, path).map(Rc::new)).as_ref().map(|a| a.clone())
    }

  fn do_load(facade: &'a dyn Facade, rl: &'a ResourceLoader, path: impl AsRef<Path>) -> Result<Program, Error> {
    let mut vert = None;
    let mut frag = None;
    let mut geom = None;
    let mut cd = CommandDispatcher::new(SimpleExecutor::new(|cmd, args| {
      match cmd {
        "vertex_shader" => vert = Some(args[0].to_owned()),
        "fragment_shader" => frag = Some(args[0].to_owned()),
        "geometry_shader" => geom = Some(args[0].to_owned()),
        _ => warn!("Invalid command in shader definition '{}': '{}'", path.as_ref().to_string_lossy(), cmd),
      }
    }));
    cd.scheduler().exec(&rl.read_to_string(&path)?, ExecSource::Other);
    cd.resume_until_empty();
    let vert = vert.ok_or(Error::MissingVertexShader)?;
    let frag = frag.ok_or(Error::MissingFragmentShader)?;
    fn do_load(facade: &'a dyn Facade, rl: &'a ResourceLoader, path: impl AsRef<Path>) -> Result<Program, Error> {
        let mut vert = None;
        let mut frag = None;
        let mut geom = None;
        let mut cd = CommandDispatcher::new(SimpleExecutor::new(|cmd, args| {
            match cmd {
                "vertex_shader" => vert = Some(args[0].to_owned()),
                "fragment_shader" => frag = Some(args[0].to_owned()),
                "geometry_shader" => geom = Some(args[0].to_owned()),
                _ => warn!("Invalid command in shader definition '{}': '{}'", path.as_ref().to_string_lossy(), cmd),
            }
        }));
        cd.scheduler().exec(&rl.read_to_string(&path)?, ExecSource::Other);
        cd.resume_until_empty();
        let vert = vert.ok_or(Error::MissingVertexShader)?;
        let frag = frag.ok_or(Error::MissingFragmentShader)?;

    let base_path = path.as_ref().parent().unwrap_or("/".as_ref());
    let vert_src = rl.read_to_string(base_path.join(vert))?;
    let frag_src = rl.read_to_string(base_path.join(frag))?;
    let geom_src = match geom {
      None => None,
      Some(path) => Some(rl.read_to_string(base_path.join(path))?),
    };
        let base_path = path.as_ref().parent().unwrap_or("/".as_ref());
        let vert_src = rl.read_to_string(base_path.join(vert))?;
        let frag_src = rl.read_to_string(base_path.join(frag))?;
        let geom_src = match geom {
            None => None,
            Some(path) => Some(rl.read_to_string(base_path.join(path))?),
        };

    Ok(Program::from_source(facade, &vert_src, &frag_src, geom_src.as_ref().map(|s| &**s))?)
  }
        Ok(Program::from_source(facade, &vert_src, &frag_src, geom_src.as_ref().map(|s| &**s))?)
    }
}

#[derive(Debug)]
pub enum Error {
  LoadError(res::Error),
  MissingVertexShader,
  MissingFragmentShader,
  ProgramCreation(ProgramCreationError),
    LoadError(res::Error),
    MissingVertexShader,
    MissingFragmentShader,
    ProgramCreation(ProgramCreationError),
}

impl From<res::Error> for Error {
  fn from(that: res::Error) -> Self { Error::LoadError(that) }
    fn from(that: res::Error) -> Self { Error::LoadError(that) }
}

impl From<ProgramCreationError> for Error {
  fn from(that: ProgramCreationError) -> Self { Error::ProgramCreation(that) }
    fn from(that: ProgramCreationError) -> Self { Error::ProgramCreation(that) }
}
\ No newline at end of file

M src/testui.rs => src/testui.rs +2 -2
@@ 3,7 3,7 @@ use std::rc::Rc;

use crate::{Model, Vertex};
use crate::cam::Camera;
use crate::cmd::{CommandDispatcher, CommandScheduler, ExecSource, ExecState, Executor};
use crate::cmd::{CommandScheduler, ExecSource, ExecState, Executor};
use crate::ui::controller::UiController;
use crate::ui::element::{Button, InputField, InputFieldEvent, Viewport};
use crate::ui::graphics::{DrawPreview, Graphics, StringDrawProps};


@@ 97,7 97,7 @@ impl UiController for TestUiController {
        InputFieldEvent::EnterPressed => {
          self.scheduler.exec(b.text(), ExecSource::Console);