Initial commit

This commit is contained in:
2022-07-29 16:21:08 +02:00
commit 313b188873
10 changed files with 657 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
//! This module helps us deal with controls and events.
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use crate::Result;
/// The state of the keyboard.
pub struct Keyboard {
/// The inner keyboard.
inner: Rc<RefCell<InnerKeyboard>>,
}
impl Keyboard {
/// Checks wether a key is pressed or not.
pub fn is_key_pressed(&self, key: Key) -> bool {
let mut b = self.inner.borrow_mut();
*b.keys.entry(key).or_insert(false)
}
/// Receives and treats an event.
pub fn manage_event(&self, event: Event) {
let mut b = self.inner.borrow_mut();
match event {
Event::KeyPressed(x) => *b.keys.entry(x).or_insert(true) = true,
Event::KeyReleased(x) => *b.keys.entry(x).or_insert(false) = false,
}
}
}
impl Keyboard {
/// Initializes the keyboard.
pub fn new(document: &web_sys::Document) -> Result<Keyboard> {
let inner = Rc::new(RefCell::new(InnerKeyboard::new()));
let clone = Keyboard {
inner: inner.clone(),
};
let down_cb = Closure::<dyn FnMut(_)>::new(move |event: web_sys::KeyboardEvent| {
if let Some(event) = Event::from_js(true, event) {
clone.manage_event(event);
}
});
document.set_onkeydown(Some(down_cb.as_ref().unchecked_ref()));
down_cb.forget();
let clone = Keyboard {
inner: inner.clone(),
};
let up_cb = Closure::<dyn FnMut(_)>::new(move |event: web_sys::KeyboardEvent| {
if let Some(event) = Event::from_js(false, event) {
clone.manage_event(event);
}
});
document.set_onkeyup(Some(up_cb.as_ref().unchecked_ref()));
up_cb.forget();
Ok(Keyboard { inner })
}
}
/// The state of the keyboard.
pub struct InnerKeyboard {
/// Holds the state of the keys.
keys: HashMap<Key, bool>,
/// The list of events to be processed.
events: Vec<Event>,
}
impl InnerKeyboard {
/// Creates a new inner arrowboard.
pub fn new() -> InnerKeyboard {
InnerKeyboard {
keys: HashMap::new(),
events: vec![],
}
}
}
/// The different events that can occur.
#[derive(Copy, Clone)]
pub enum Event {
/// A key was pressed down.
KeyPressed(Key),
/// A key was released.
KeyReleased(Key),
}
/// The different key we cant to support.
#[derive(Hash, PartialEq, Eq, Copy, Clone)]
pub enum Key {
/// The left arrow key.
ArrowLeft,
/// The right arrow key.
ArrowRight,
/// The up arrow key.
ArrowUp,
/// The bottom arrow key.
ArrowDown,
}
impl Key {
/// Tries and converts a javascript key to our key type.
pub fn from_str(string: &str) -> Option<Key> {
match string {
"ArrowLeft" => Some(Key::ArrowLeft),
"ArrowRight" => Some(Key::ArrowRight),
"ArrowUp" => Some(Key::ArrowUp),
"ArrowDown" => Some(Key::ArrowDown),
_ => None,
}
}
}
impl Event {
/// Tries and converts a javacript event to our event type.
pub fn from_js(down: bool, event: web_sys::KeyboardEvent) -> Option<Event> {
let key = Key::from_str(&event.code())?;
let event = if down {
Some(Event::KeyPressed(key))
} else {
Some(Event::KeyReleased(key))
};
event
}
}
+77
View File
@@ -0,0 +1,77 @@
//! A module that makes it easier to deal with images.
use std::borrow::BorrowMut;
use std::cell::{Ref, RefCell};
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::HtmlImageElement;
use crate::{log, Result};
/// A wrapper to make it easier to manage HTML images.
pub struct Image {
/// The inner image.
pub inner: Rc<RefCell<InnerImage>>,
}
/// The content of the image.
pub struct InnerImage {
/// The inner HtmlImageElement.
pub inner: HtmlImageElement,
/// Whether the image has been loaded or not.
pub loaded: bool,
}
impl Image {
/// Loads a new image from its src.
pub fn new(path: &str) -> Result<Image> {
let image = HtmlImageElement::new()?;
image.set_src(path);
let image = Image {
inner: Rc::new(RefCell::new(InnerImage {
inner: image,
loaded: false,
})),
};
let clone = image.inner.clone();
let path_clone = path.to_string();
let cb = Closure::<dyn FnMut(_)>::new(move |_event: web_sys::Event| {
log!("{} loaded", path_clone);
(*clone).borrow_mut().loaded = true;
});
image
.inner
.borrow()
.inner
.set_onload(Some(cb.as_ref().unchecked_ref()));
cb.forget();
Ok(image)
}
/// Returns whether the image is loaded.
pub fn is_loaded(&self) -> bool {
self.inner.borrow().loaded
}
/// Renders the image on a context.
pub fn render(
&self,
x: f64,
y: f64,
context: &web_sys::CanvasRenderingContext2d,
) -> Result<()> {
context.draw_image_with_html_image_element(&self.inner.borrow().inner, x, y)?;
Ok(())
}
}
+196
View File
@@ -0,0 +1,196 @@
//! This module contains the whole engine that manages everything.
pub mod event;
pub mod image;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use crate::engine::event::{Key, Keyboard};
use crate::engine::image::Image;
use crate::Result;
/// Our game engine.
pub struct Engine {
/// The inner engine.
///
/// We need Rc<RefCell> in order to deal with events.
pub inner: Rc<RefCell<InnerEngine>>,
/// The web page document.
pub document: Rc<web_sys::Document>,
/// The canvas rendering context.
///
/// We keep a reference so that we can easily render things.
pub context: Rc<web_sys::CanvasRenderingContext2d>,
}
impl Engine {
/// Creates a new engine.
pub fn new() -> Result<Engine> {
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document.get_element_by_id("canvas").unwrap();
let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into::<web_sys::HtmlCanvasElement>()?;
canvas.set_width(1920);
canvas.set_height(1080);
let context = canvas
.get_context("2d")?
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
let inner = InnerEngine::new(&document)?;
let document = Rc::new(document);
let context = Rc::new(context);
Ok(Engine {
inner: Rc::new(RefCell::new(inner)),
document,
context,
})
}
/// Clones the engine.
///
/// It is made to be efficient, cloning only Rcs.
pub fn clone(&self) -> Engine {
Engine {
inner: self.inner.clone(),
document: self.document.clone(),
context: self.context.clone(),
}
}
/// Starts the engine.
pub fn start(&self) -> Result<()> {
let clone = self.clone();
let cb = Closure::<dyn FnMut(_)>::new(move |event: web_sys::Event| {
clone.run();
});
web_sys::window()
.unwrap()
.request_animation_frame(cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
Ok(())
}
/// Launches a loop of the engine, and schedules the next one.
pub fn run(&self) -> Result<()> {
// Perform update
self.update()?;
// Perform render
self.render()?;
// Schedule next render
let clone = self.clone();
let cb = Closure::<dyn FnMut(_)>::new(move |event: web_sys::Event| {
clone.run();
});
web_sys::window()
.unwrap()
.request_animation_frame(cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
Ok(())
}
/// Triggers the update of the model of the engine.
pub fn update(&self) -> Result<()> {
let mut inner = self.inner.borrow_mut();
// Perform update
let mut dx = 0.0;
let mut dy = 0.0;
if inner.keyboard.is_key_pressed(Key::ArrowDown) {
dy += 5.0;
}
if inner.keyboard.is_key_pressed(Key::ArrowUp) {
dy -= 5.0;
}
if inner.keyboard.is_key_pressed(Key::ArrowLeft) {
dx -= 5.0;
}
if inner.keyboard.is_key_pressed(Key::ArrowRight) {
dx += 5.0;
}
inner.x += dx;
inner.y += dy;
Ok(())
}
/// Performs the rendering of the engine.
pub fn render(&self) -> Result<()> {
let inner = self.inner.borrow();
// Perform render
self.context.clear_rect(0.0, 0.0, 1920.0, 1080.0);
inner
.textures
.test
.render(inner.x, inner.y, &self.context)?;
Ok(())
}
}
/// The data contained in our engine.
pub struct InnerEngine {
/// The x position of the drawing.
pub x: f64,
/// The y position of the drawing.
pub y: f64,
/// The keyboard.
pub keyboard: Keyboard,
/// The texture manager.
pub textures: TextureManager,
}
impl InnerEngine {
/// Initializes the engine.
pub fn new(document: &web_sys::Document) -> Result<InnerEngine> {
Ok(InnerEngine {
x: 0.0,
y: 0.0,
keyboard: Keyboard::new(document)?,
textures: TextureManager::new()?,
})
}
}
/// Our texture manager.
///
/// It holds all our resources.
pub struct TextureManager {
test: Image,
}
impl TextureManager {
/// Creates and start the loading of all our textures.
fn new() -> Result<TextureManager> {
let test = Image::new("static/image.png")?;
Ok(TextureManager { test })
}
}
+40
View File
@@ -0,0 +1,40 @@
use std::rc::Rc;
use std::result::Result as StdResult;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
pub mod engine;
/// A type that makes dealing with results easier.
pub type Result<T> = StdResult<T, JsValue>;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console, js_name = log)]
pub fn console_log(s: &str);
#[wasm_bindgen(js_namespace = console, js_name = log)]
pub fn console_log_js(s: JsValue);
}
macro_rules! log {
($($t:tt)*) => (crate::console_log(&format_args!($($t)*).to_string()))
}
macro_rules! log_js {
($t:tt) => {{
let e: JsValue = $t.into();
crate::console_log_js(e)
}};
}
pub(crate) use log;
pub(crate) use log_js;
#[wasm_bindgen(start)]
pub fn start() -> Result<()> {
let mut engine = engine::Engine::new()?;
engine.start()?;
Ok(())
}