Browse Source

Implemented basic rendering

raster
Bergmann89 4 years ago
parent
commit
2b65dcde29
9 changed files with 1645 additions and 223 deletions
  1. +971
    -51
      Cargo.lock
  2. +3
    -0
      Cargo.toml
  3. +20
    -0
      gl/Cargo.toml
  4. +537
    -0
      gl/build.rs
  5. +10
    -0
      gl/src/bindings.rs
  6. +24
    -0
      gl/src/gl.rs
  7. +9
    -0
      gl/src/lib.rs
  8. +3
    -1
      space-crush/Cargo.toml
  9. +68
    -171
      space-crush/src/main.rs

+ 971
- 51
Cargo.lock
File diff suppressed because it is too large
View File


+ 3
- 0
Cargo.toml View File

@@ -4,6 +4,7 @@ members = [
"async-ecs",
"async-ecs-derive",
"space-crush",
"gl",
]
default-members = [
"space-crush",
@@ -13,3 +14,5 @@ default-members = [
asparit = { path = "./asparit" }
async-ecs = { path = "./async-ecs" }
async-ecs-derive = { path = "./async-ecs-derive" }
space-crush = { path = "./space-crush" }
gl = { path = "./gl" }

+ 20
- 0
gl/Cargo.toml View File

@@ -0,0 +1,20 @@
[package]
name = "gl"
version = "0.1.0"
authors = [ "Bergmann89 <info@bergmann89.de>" ]
edition = "2018"

[features]
generate_debug = [ ]
generate_global = [ ]
generate_struct = [ ]
use_log_crate = [ "log" ]
default = [ ]

[dependencies]
log = { version = "0.4", optional = true }

[build-dependencies]
gl_generator = { version = "0.14", features = [ "unstable_generator_utils" ] }
lazy_static = "1.4"
regex = "1.4"

+ 537
- 0
gl/build.rs View File

@@ -0,0 +1,537 @@
#![allow(clippy::trivial_regex)]
#![allow(clippy::useless_format)]

extern crate gl_generator;

use std::env::var;
use std::fs::File;
use std::io::{Error as IoError, Write};
use std::path::Path;

use gl_generator::{
generators::{gen_struct_name, gen_symbol_name, gen_types},
Api, Cmd, Fallbacks, Generator as GlGenerator, Profile, Registry,
};
use lazy_static::lazy_static;
use regex::{Captures, Regex};

fn main() {
let out_dir = var("OUT_DIR").unwrap();
let mut file = File::create(&Path::new(&out_dir).join("bindings.rs")).unwrap();

let generator = Generator {
generate_debug: var("CARGO_FEATURE_GENERATE_DEBUG").is_ok(),
generate_global: var("CARGO_FEATURE_GENERATE_GLOBAL").is_ok(),
generate_struct: var("CARGO_FEATURE_GENERATE_STRUCT").is_ok(),
log_fn: if var("CARGO_FEATURE_USE_LOG_CRATE").is_ok() {
"log::trace!"
} else {
"println!"
},
};

Registry::new(Api::Gl, (4, 5), Profile::Core, Fallbacks::All, [])
.write_bindings(generator, &mut file)
.unwrap();
}

pub struct Generator {
generate_debug: bool,
generate_global: bool,
generate_struct: bool,
log_fn: &'static str,
}

impl GlGenerator for Generator {
fn write<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
self.write_header(dest)?;
self.write_type_aliases(registry, dest)?;
self.write_enums(registry, dest)?;
self.write_fnptr_struct_def(registry, dest)?;

if self.generate_global {
self.write_load_fn(registry, dest)?;
self.write_fns(registry, dest)?;
self.write_ptrs(registry, dest)?;
self.write_fn_mods(registry, dest)?;
}

if self.generate_struct {
self.write_struct_def(registry, dest)?;
self.write_struct_impl(registry, dest)?;
}

Ok(())
}
}

impl Generator {
fn write_header<W>(&self, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
writeln!(
dest,
r#"
mod __gl_imports {{
pub use std::os::raw;
pub use std::mem::transmute;
}}"#
)?;

Ok(())
}

fn write_type_aliases<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
writeln!(
dest,
r#"
pub mod types {{"#
)?;

gen_types(registry.api, dest)?;

writeln!(
dest,
r#"
}}"#
)?;

Ok(())
}

fn write_enums<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
for enm in &registry.enums {
writeln!(
dest,
r#"pub const {ident}: {types_prefix}{ty} = {value}{cast_suffix};"#,
ident = enm.ident,
types_prefix = if enm.ty == "&'static str" {
""
} else {
"types::"
},
ty = enm.ty,
value = enm.value,
cast_suffix = match enm.cast {
true => format!(" as types::{}", enm.ty),
false => String::new(),
},
)?;
}

Ok(())
}

fn write_fnptr_struct_def<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
writeln!(
dest,
r#"
#[derive(Clone)]
pub struct FnPtr {{
f: *const std::os::raw::c_void,
is_loaded: bool,
}}

impl FnPtr {{
fn new(ptr: *const std::os::raw::c_void) -> FnPtr {{
if ptr.is_null() {{
FnPtr {{
f: missing_fn_panic as *const std::os::raw::c_void,
is_loaded: false
}}
}} else {{
FnPtr {{ f: ptr, is_loaded: true }}
}}
}}

#[inline]
pub fn is_loaded(&self) -> bool {{
self.is_loaded
}}
}}

#[inline(never)]
fn missing_fn_panic() -> ! {{
panic!("{api} function was not loaded")
}}

#[inline(never)]
fn metaloadfn(
loadfn: &mut dyn FnMut(&'static str) -> *const std::os::raw::c_void,
symbol: &'static str,
fallbacks: &[&'static str]) -> *const std::os::raw::c_void
{{
let mut ptr = loadfn(symbol);

if ptr.is_null() {{
for &sym in fallbacks {{
ptr = loadfn(sym);
if !ptr.is_null() {{ break; }}
}}
}}

ptr
}}"#,
api = registry.api,
)
}

fn write_load_fn<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
write!(
dest,
r#"
pub fn load_with<F>(mut loadfn: F) where F: FnMut(&'static str) -> *const __gl_imports::raw::c_void {{
#[inline(never)]
fn inner(loadfn: &mut dyn FnMut(&'static str) -> *const __gl_imports::raw::c_void) {{"#
)?;

for c in &registry.cmds {
write!(
dest,
r#"
{cmd_name}::load_with(&mut *loadfn);"#,
cmd_name = to_snake_case(&c.proto.ident),
)?;
}

writeln!(
dest,
r#"
}}

inner(&mut loadfn)
}}"#
)?;

Ok(())
}

fn write_fns<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
let has_get_error = registry
.cmds
.iter()
.any(|cmd| cmd.proto.ident == "GetError");

for cmd in &registry.cmds {
self.gen_func(dest, cmd, has_get_error, true)?;
}

Ok(())
}

fn write_ptrs<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
writeln!(
dest,
r#"
mod storage {{
use super::__gl_imports::raw;
use super::FnPtr;"#
)?;

for c in &registry.cmds {
writeln!(
dest,
r#"
pub static mut {name}: FnPtr = FnPtr {{
f: super::missing_fn_panic as *const raw::c_void,
is_loaded: false
}};"#,
name = to_snake_case(&c.proto.ident)
)?;
}

writeln!(
dest,
r#"
}}"#
)?;

Ok(())
}

fn write_fn_mods<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
for c in &registry.cmds {
let fallbacks = match registry.aliases.get(&c.proto.ident) {
Some(v) => {
let names = v
.iter()
.map(|name| format!("\"{}\"", gen_symbol_name(registry.api, &name)))
.collect::<Vec<_>>();
format!("&[{}]", names.join(", "))
}
None => "&[]".to_string(),
};
let fnname = to_snake_case(&c.proto.ident);
let symbol = gen_symbol_name(registry.api, &c.proto.ident);
let symbol = &symbol;

writeln!(
dest,
r##"
pub mod {fnname} {{
use super::{{storage, metaloadfn}};
use super::__gl_imports::raw;
use super::FnPtr;

#[inline]
pub fn is_loaded() -> bool {{
unsafe {{ storage::{fnname}.is_loaded }}
}}

pub fn load_with<F>(mut loadfn: F) where F: FnMut(&'static str) -> *const raw::c_void {{
unsafe {{
storage::{fnname} = FnPtr::new(metaloadfn(&mut loadfn, "{symbol}", {fallbacks}))
}}
}}
}}"##,
fnname = fnname,
fallbacks = fallbacks,
symbol = symbol
)?;
}

Ok(())
}

fn write_struct_def<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
writeln!(
dest,
r#"
#[derive(Clone)]
pub struct {api} {{"#,
api = gen_struct_name(registry.api)
)?;

for cmd in &registry.cmds {
writeln!(
dest,
r#" pub {name}: FnPtr,"#,
name = to_snake_case(&cmd.proto.ident)
)?;
}

writeln!(dest, r#" }}"#)?;

Ok(())
}

fn write_struct_impl<W>(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError>
where
W: Write,
{
write!(
dest,
r#"
impl {api} {{
pub fn load_with<F>(mut loadfn: F) -> {api} where F: FnMut(&'static str) -> *const std::os::raw::c_void {{
let mut do_loadfn = |symbol: &'static str, symbols: &[&'static str]| {{
metaloadfn(&mut loadfn, symbol, symbols)
}};

{api} {{"#,
api = gen_struct_name(registry.api)
)?;

for cmd in &registry.cmds {
write!(
dest,
r#"
{name}: FnPtr::new(do_loadfn("{symbol}", &[{fallbacks}])),"#,
name = to_snake_case(&cmd.proto.ident),
symbol = gen_symbol_name(registry.api, &cmd.proto.ident),
fallbacks = match registry.aliases.get(&cmd.proto.ident) {
Some(fbs) => fbs
.iter()
.map(|name| format!(r#""{}""#, gen_symbol_name(registry.api, &name)))
.collect::<Vec<_>>()
.join(", "),
None => format!(""),
},
)?
}

writeln!(
dest,
r#"
}}
}}"#
)?;

let has_get_error = registry
.cmds
.iter()
.any(|cmd| cmd.proto.ident == "GetError");

for cmd in &registry.cmds {
self.gen_func(dest, cmd, has_get_error, false)?;
}

writeln!(
dest,
r#"
}}

unsafe impl std::marker::Send for {api} {{}}"#,
api = gen_struct_name(registry.api)
)
}

fn gen_func<W>(
&self,
dest: &mut W,
cmd: &Cmd,
has_get_error: bool,
is_global: bool,
) -> Result<(), IoError>
where
W: Write,
{
let ident = to_snake_case(&cmd.proto.ident);
let idents = gen_parameters(cmd, true, false);
let typed_params = gen_parameters(cmd, false, true);
let mut print_ln = String::new();
let mut print_err = String::new();

if self.generate_debug {
print_ln = format!(
r#"
{log_fn}("[OpenGL] {}({})" {});
"#,
ident,
(0..idents.len())
.map(|_| "{:?}".to_string())
.collect::<Vec<_>>()
.join(", "),
idents
.iter()
.zip(typed_params.iter())
.map(|(name, ty)| if ty.contains("GLDEBUGPROC") {
format!(r#", "<callback>""#)
} else {
format!(r#", {}"#, name)
})
.collect::<Vec<_>>()
.concat(),
log_fn = self.log_fn,
);

if cmd.proto.ident != "GetError" && has_get_error {
print_err = format!(
r#"
let __get_err = __gl_imports::transmute::<_, extern "system" fn() -> u32>({this}get_error.f);
match __get_err() {{
0 => (),
r => {log_fn}("[OpenGL] ^ GL error triggered: {{}}", r),
}}
"#,
this = if is_global { "storage::" } else { "self." },
log_fn = self.log_fn,
);
}
}

write!(
dest,
r#"
#[inline]
pub fn {name}({this_ty}{params}) -> {return_suffix} {{
unsafe {{{print_ln}
let __f = __gl_imports::transmute::<_, extern "system" fn({typed_params}) -> {return_suffix}>({this}{name}.f);
let __r = __f({idents});
{print_err}
__r
}}
}}"#,
this_ty = if is_global { "" } else { "&self, " },
this = if is_global { "storage::" } else { "self." },
name = ident,
params = gen_parameters(cmd, true, true).join(", "),
typed_params = typed_params.join(", "),
return_suffix = cmd.proto.ty,
idents = idents.join(", "),
print_ln = print_ln,
print_err = print_err,
)
}
}

pub fn gen_parameters(cmd: &Cmd, with_idents: bool, with_types: bool) -> Vec<String> {
cmd.params
.iter()
.map(|binding| {
if with_idents && with_types {
format!("{}: {}", to_snake_case(&binding.ident), binding.ty)
} else if with_types {
format!("{}", binding.ty)
} else if with_idents {
format!("{}", to_snake_case(&binding.ident))
} else {
panic!()
}
})
.collect()
}

fn to_snake_case(s: &str) -> String {
lazy_static! {
static ref RX01: Regex = Regex::new(r"([a-z])([A-Z])").unwrap();
static ref RX02: Regex = Regex::new(r"([a-z])([0-9][0-9A-Za-z]+)$").unwrap();
static ref RX03: Regex = Regex::new(r"([A-Z])([A-Z][a-z]+)").unwrap();
static ref RX04: Regex = Regex::new(r"([A-Z])([0-9])").unwrap();
static ref RX05: Regex = Regex::new(r"([A-Z][a-z]*)(uiv|usv)$").unwrap();
static ref RX06: Regex = Regex::new(r"([A-Z][a-z]*)(dv|fv|iv)$").unwrap();
static ref RX07: Regex = Regex::new(r"([A-Z][a-z]*)(v|f|i)$").unwrap();
static ref RX08: Regex = Regex::new(r"i_64").unwrap();
static ref RX09: Regex = Regex::new(r"u_i64").unwrap();
static ref RX10: Regex = Regex::new(r"f_i").unwrap();
static ref RX11: Regex = Regex::new(r"([a-z]{3,})i$").unwrap();
static ref RX12: Regex = Regex::new(r"([a-z])i_v").unwrap();
static ref RX13: Regex = Regex::new(r"([a-z])i64_v").unwrap();
static ref RX14: Regex = Regex::new(r"[Ii]nteger_64").unwrap();
}

let s = RX01.replace_all(s, |c: &Captures| format!("{}_{}", &c[1], &c[2]));
let s = RX02.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2]));
let s = RX03.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2]));
let s = RX04.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2]));
let s = RX05.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2]));
let s = RX06.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2]));
let s = RX07.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2]));
let s = RX08.replace_all(&s, "_i64");
let s = RX09.replace_all(&s, "_ui64");
let s = RX10.replace_all(&s, "_fi");
let s = RX11.replace_all(&s, |c: &Captures| format!("{}_i", &c[1]));
let s = RX12.replace_all(&s, |c: &Captures| format!("{}_i_v", &c[1]));
let s = RX13.replace_all(&s, |c: &Captures| format!("{}_i64_v", &c[1]));
let s = RX14.replace_all(&s, "Integer64");

s.into_owned().to_lowercase()
}

+ 10
- 0
gl/src/bindings.rs View File

@@ -0,0 +1,10 @@
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(clippy::unused_unit)]
#![allow(clippy::let_and_return)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::manual_non_exhaustive)]
#![allow(clippy::many_single_char_names)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

+ 24
- 0
gl/src/gl.rs View File

@@ -0,0 +1,24 @@
use std::ops::Deref;
use std::rc::Rc;

use super::bindings::InnerGl;

#[derive(Clone)]
pub struct Gl(Rc<InnerGl>);

impl Gl {
pub fn load_with<F>(f: F) -> Self
where
F: FnMut(&'static str) -> *const types::GLvoid,
{
Gl(Rc::new(InnerGl::load_with(f)))
}
}

impl Deref for Gl {
type Target = InnerGl;

fn deref(&self) -> &Self::Target {
&self.0
}
}

+ 9
- 0
gl/src/lib.rs View File

@@ -0,0 +1,9 @@
mod bindings;
#[cfg(feature = "generate_struct")]
mod gl;

#[cfg(feature = "generate_struct")]
pub use bindings::Gl as InnerGl;
pub use bindings::*;
#[cfg(feature = "generate_struct")]
pub use gl::*;

+ 3
- 1
space-crush/Cargo.toml View File

@@ -10,7 +10,9 @@ async-ecs = "0.1"
async-ecs-derive = "0.1"
env_logger = "0.8"
futures = "0.3"
log = "0.4"
gl = { version = "0.1", features = [ "use_log_crate", "generate_global" ] }
glutin = "0.25"
log = { version = "0.4", features = [ "max_level_debug", "release_max_level_warn" ] }
num_cpus = "1.13"
rand = "0.7"
thiserror = "1.0"


+ 68
- 171
space-crush/src/main.rs View File

@@ -1,22 +1,20 @@
use std::io::Error as IoError;
use std::time::{Duration, Instant};

use async_ecs::{
asparit::{Driver, ParallelIterator},
dispatcher::Error as DispatcherError,
AsyncSystem, Builder as EntityBuilder, Dispatcher, Join, ParJoin, ReadStorage, System,
VecStorage, World, WriteStorage,
use glutin::{
dpi::{PhysicalPosition, PhysicalSize},
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::desktop::EventLoopExtDesktop,
window::WindowBuilder,
ContextBuilder, CreationError, GlProfile,
};
use async_ecs_derive::Component;
use futures::future::{BoxFuture, FutureExt};
use log::info;
use rand::random;
use thiserror::Error;
use tokio::{runtime::Builder, task::LocalSet};

fn main() -> Result<(), Error> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.filter_level(log::LevelFilter::Debug)
.format_timestamp_nanos()
.init();

@@ -29,177 +27,73 @@ fn main() -> Result<(), Error> {
}

async fn run() -> Result<(), Error> {
info!("Application started!");
info!(" num_cpus = {}", num_cpus::get());

let mut world = World::default();
world.register_component::<Position>();
world.register_component::<Velocity>();
world.register_component::<Acceleration>();

for _ in 0..ENTITY_COUNT {
world
.create_entity()
.with(Velocity::random())
.with(Position::random())
.with(Acceleration::random())
.build();
}

info!("World initialized!");

let mut dispatcher = Dispatcher::builder()
.with_async(ParMove, "move", &[])?
.with_async(ParAccelerate, "accelerate", &["move"])?
.build();

info!("Setup done!");

let mut delta = Duration::from_secs(0);
for _ in 0..REPEAT_COUNT {
info!("Start iteration");

let start = Instant::now();

dispatcher.dispatch(&world).await?;
world.maintain().await;

let end = Instant::now();
let mut event_loop = EventLoop::new();
let window_builder = WindowBuilder::new()
.with_title("space-crush")
.with_visible(true)
.with_inner_size(PhysicalSize::new(WINDOW_WIDTH, WINDOW_HEIGHT));
let context = ContextBuilder::new()
.with_double_buffer(Some(true))
.with_hardware_acceleration(Some(true))
.with_pixel_format(24, 8)
.with_vsync(true)
.with_gl_profile(GlProfile::Core)
.build_windowed(window_builder, &event_loop)?;

let window = context.window();
let window_size = window.outer_size();
let monitor_size = window.current_monitor().unwrap().size();
window.set_outer_position(PhysicalPosition::new(
(monitor_size.width - window_size.width) / 2,
(monitor_size.height - window_size.height) / 2,
));

let context = unsafe { context.make_current().unwrap() };

gl::load_with(|s| context.get_proc_address(s));
let mut run = true;

loop {
event_loop.run_return(|event, _target, flow_control| {
*flow_control = ControlFlow::Poll;

match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
run = false;
*flow_control = ControlFlow::Exit;
}
Event::MainEventsCleared | Event::RedrawRequested(_) => {
*flow_control = ControlFlow::Exit;
}
_ => (),
}
});

if !run {
break;
}

info!("End iteration");
gl::clear_color(0.1, 0.1, 0.1, 1.0);
gl::clear(gl::COLOR_BUFFER_BIT);

delta += end - start;
context.swap_buffers().unwrap();
}

let delta = delta / REPEAT_COUNT;

info!("Average time per dispatch: {:?}", delta);
info!("finished");

Ok(())
}

const ENTITY_COUNT: usize = 1_000_000;
const REPEAT_COUNT: u32 = 100;

#[derive(Debug, Component)]
#[storage(VecStorage)]
struct Position {
x: f64,
y: f64,
}

#[derive(Debug, Component)]
#[storage(VecStorage)]
struct Velocity {
x: f64,
y: f64,
}

#[derive(Debug, Component)]
#[storage(VecStorage)]
struct Acceleration {
x: f64,
y: f64,
}

struct SeqMove;
struct SeqAccelerate;

struct ParMove;
struct ParAccelerate;

#[derive(Debug, Error)]
enum Error {
#[error("IO Error: {0}")]
IoError(IoError),

#[error("Dispatcher Error: {0}")]
DispatcherError(DispatcherError),
}

impl Position {
fn random() -> Self {
Self {
x: random::<f64>() - 0.5,
y: random::<f64>() - 0.5,
}
}
}

impl Velocity {
fn random() -> Self {
Self {
x: random::<f64>() - 0.5,
y: random::<f64>() - 0.5,
}
}
}

impl Acceleration {
fn random() -> Self {
Self {
x: random::<f64>() - 0.5,
y: random::<f64>() - 0.5,
}
}
}

impl<'a> System<'a> for SeqMove {
type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>);

fn run(&mut self, (mut position, velocity): Self::SystemData) {
for (position, velocity) in (&mut position, &velocity).join() {
position.x += velocity.x;
position.y += velocity.y;
}
}
}

impl<'a> System<'a> for SeqAccelerate {
type SystemData = (WriteStorage<'a, Velocity>, ReadStorage<'a, Acceleration>);

fn run(&mut self, (mut velocity, acceleration): Self::SystemData) {
for (velocity, acceleration) in (&mut velocity, &acceleration).join() {
velocity.x += acceleration.x;
velocity.y += acceleration.y;
}
}
}

impl<'a> AsyncSystem<'a> for ParMove {
type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>);

fn run_async(&mut self, (mut position, velocity): Self::SystemData) -> BoxFuture<'a, ()> {
async move {
(&mut position, &velocity)
.par_join()
.for_each(|(position, velocity)| {
position.x += velocity.x;
position.y += velocity.y;
})
.exec()
.await;
}
.boxed()
}
}

impl<'a> AsyncSystem<'a> for ParAccelerate {
type SystemData = (WriteStorage<'a, Velocity>, ReadStorage<'a, Acceleration>);

fn run_async(&mut self, (mut velocity, acceleration): Self::SystemData) -> BoxFuture<'a, ()> {
async move {
(&mut velocity, &acceleration)
.par_join()
.for_each(|(velocity, acceleration)| {
velocity.x += acceleration.x;
velocity.y += acceleration.y;
})
.exec()
.await;
}
.boxed()
}
#[error("OpenGL Context Error: {0}")]
OpenGlContext(CreationError),
}

impl From<IoError> for Error {
@@ -208,8 +102,11 @@ impl From<IoError> for Error {
}
}

impl From<DispatcherError> for Error {
fn from(err: DispatcherError) -> Self {
Self::DispatcherError(err)
impl From<CreationError> for Error {
fn from(err: CreationError) -> Self {
Self::OpenGlContext(err)
}
}

const WINDOW_WIDTH: u32 = 1280;
const WINDOW_HEIGHT: u32 = 720;

Loading…
Cancel
Save