Initial commit

This commit is contained in:
Thomas Forgione 2022-07-29 16:21:08 +02:00
commit 313b188873
10 changed files with 657 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
pkg
target

147
Cargo.lock generated Normal file
View File

@ -0,0 +1,147 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bumpalo"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "game"
version = "0.1.0"
dependencies = [
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "js-sys"
version = "0.3.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "proc-macro2"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
[[package]]
name = "wasm-bindgen"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
[[package]]
name = "web-sys"
version = "0.3.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
dependencies = [
"js-sys",
"wasm-bindgen",
]

25
Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "game"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
js-sys = "0.3.51"
wasm-bindgen = "0.2.74"
[dependencies.web-sys]
version = "0.3.4"
features = [
'CanvasRenderingContext2d',
'Document',
'Element',
'Event',
'KeyboardEvent',
'HtmlCanvasElement',
'HtmlImageElement',
'Window',
]

8
Makefile Normal file
View File

@ -0,0 +1,8 @@
all:
wasm-pack build --target web
deploy: all
ssh pi -C "rm -rf /mnt/www/storage.tforgione.fr/game"
ssh pi -C "mkdir /mnt/www/storage.tforgione.fr/game"
scp index.html pi:/mnt/www/storage.tforgione.fr/game
scp -r static pkg pi:/mnt/www/storage.tforgione.fr/game

21
index.html Normal file
View File

@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
<style>
* {
padding: 0px;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="module">
import init from './pkg/game.js';
async function run() { await init(); }
run();
</script>
</body>
</html>

141
src/engine/event.rs Normal file
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
src/engine/image.rs Normal file
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
src/engine/mod.rs Normal file
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
src/lib.rs Normal file
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(())
}

BIN
static/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB