Skip to content

Commit

Permalink
add wasm-backend
Browse files Browse the repository at this point in the history
* add wasm-backend

* add wasm configuration and replace sleep

* add wasm-backend

* fix busy_waiting

* fix busy waiting

* add buffer to print

* add js module

* change passing parameter as pointer
  • Loading branch information
genieCS committed Jul 25, 2023
1 parent e219ed8 commit 5db16fc
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 77 deletions.
18 changes: 17 additions & 1 deletion cursive-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,30 @@ optional = true
version = "0.2"
features = ["js"]


[dependencies.web-sys]
optional = true
version = "0.3.64"
features = [
"Window",
]

[dependencies.wasm-bindgen]
optional = true
version = "0.2.87"

[dependencies.wasm-bindgen-futures]
optional = true
version = "0.4.37"

[features]
default = ["wasm"]
doc-cfg = []
builder = ["inventory", "cursive-macros/builder"]
markdown = ["pulldown-cmark"]
ansi = ["ansi-parser"]
unstable_scroll = [] # Deprecated feature, remove in next version
wasm = ["js-sys", "getrandom"]
wasm = ["js-sys", "getrandom", "web-sys", "wasm-bindgen", "wasm-bindgen-futures"]

[lib]
name = "cursive_core"
24 changes: 24 additions & 0 deletions cursive-core/src/cursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,17 @@ impl Cursive {
self.try_run_with::<(), _>(|| Ok(backend_init())).unwrap();
}

/// Initialize the backend and runs the event loop.
///
/// Used for infallible backend initializers.
#[cfg(feature = "wasm")]
pub async fn run_with_async<F>(&mut self, backend_init: F)
where
F: FnOnce() -> Box<dyn backend::Backend>,
{
self.try_run_with_async::<(), _>(|| Ok(backend_init())).await.unwrap();
}

/// Initialize the backend and runs the event loop.
///
/// Returns an error if initializing the backend fails.
Expand All @@ -892,6 +903,19 @@ impl Cursive {
Ok(())
}

/// try run with async
#[cfg(feature = "wasm")]
pub async fn try_run_with_async<E, F>(&mut self, backend_init: F) -> Result<(), E>
where
F: FnOnce() -> Result<Box<dyn backend::Backend>, E>,
{
let mut runner = self.runner(backend_init()?);

runner.run_async().await;

Ok(())
}

/// Stops the event loop.
pub fn quit(&mut self) {
self.running = false;
Expand Down
75 changes: 67 additions & 8 deletions cursive-core/src/cursive_run.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::{backend, event::Event, theme, Cursive, Vec2};
use std::borrow::{Borrow, BorrowMut};
#[cfg(not(feature = "wasm"))]
use std::time::Duration;

#[cfg(feature = "wasm")]
Expand Down Expand Up @@ -184,18 +183,59 @@ where
}
}

#[cfg(not(feature = "wasm"))]
/// post_events asynchronously
#[cfg(feature = "wasm")]
pub async fn post_events_async(&mut self, received_something: bool) {
let boring = !received_something;
// How many times should we try if it's still boring?
// Total duration will be INPUT_POLL_DELAY_MS * repeats
// So effectively fps = 1000 / INPUT_POLL_DELAY_MS / repeats
if !boring
|| self
.fps()
.map(|fps| 1000 / INPUT_POLL_DELAY_MS as u32 / fps.get())
.map(|repeats| self.boring_frame_count >= repeats)
.unwrap_or(false)
{
// We deserve to draw something!

if boring {
// We're only here because of a timeout.
self.on_event(Event::Refresh);
self.process_pending_backend_calls();
}

self.refresh();
}

if boring {
self.sleep_async().await;
self.boring_frame_count += 1;
}
}

fn sleep(&self) {
std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS));
}

#[cfg(feature = "wasm")]
fn sleep(&self) {
let start = Date::now();
let mut now = start;
while (now - start) < INPUT_POLL_DELAY_MS as f64 {
now = Date::now();
}
async fn sleep_async(&self) {
use wasm_bindgen::prelude::*;
let promise = js_sys::Promise::new(&mut |resolve, _| {
let closure = Closure::new(move || {
resolve.call0(&JsValue::null()).unwrap();
}) as Closure<dyn FnMut()>;
web_sys::window()
.expect("window is None for sleep")
.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
INPUT_POLL_DELAY_MS as i32,
)
.expect("should register timeout for sleep");
closure.forget();
});
let js_future = wasm_bindgen_futures::JsFuture::from(promise);
js_future.await.expect("should await sleep");
}

/// Refresh the screen with the current view tree state.
Expand Down Expand Up @@ -235,6 +275,14 @@ where
received_something
}

/// step asynchronously
#[cfg(feature = "wasm")]
pub async fn step_async(&mut self) -> bool {
let received_something = self.process_events();
self.post_events_async(received_something).await;
received_something
}

/// Runs the event loop.
///
/// It will wait for user input (key presses)
Expand All @@ -256,4 +304,15 @@ where
self.step();
}
}

/// Runs the event loop asynchronously.
#[cfg(feature = "wasm")]
pub async fn run_async(&mut self) {
self.refresh();

// And the big event loop begins!
while self.is_running() {
self.step_async().await;
}
}
}
6 changes: 5 additions & 1 deletion cursive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ optional = true
version = "0.2"
features = ["js"]

[dependencies.serde-wasm-bindgen]
optional = true
version = "0.5.0"


[features]
doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature.
Expand All @@ -83,7 +87,7 @@ markdown = ["cursive_core/markdown"]
ansi = ["cursive_core/ansi"]
unstable_scroll = [] # Deprecated feature, remove in next version
toml = ["cursive_core/toml"]
wasm-backend = ["wasm-bindgen", "web-sys", "cursive_core/wasm", "getrandom"]
wasm-backend = ["wasm-bindgen", "web-sys", "cursive_core/wasm", "getrandom", "serde-wasm-bindgen"]

[lib]
name = "cursive"
Expand Down
48 changes: 48 additions & 0 deletions cursive/src/backends/canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const fontWidth = 12;
const fontHeight = fontWidth * 2;
const textColorPairSize = 12;

export function paint(buffer) {
const data = new Uint8Array(buffer);
const canvas = document.getElementById('cursive-wasm-canvas');
const context = canvas.getContext('2d');
const backBuffer = new Map();
const frontBuffer = new Map();
context.font = `${fontHeight}px monospace`;
for (let x = 0; x < 1000; x++) {
for (let y = 0; y < 1000; y++) {
const n = 1000 * y + x;
const textColorPair = data.slice(n * textColorPairSize, (n + 1) * textColorPairSize);
const text = String.fromCharCode(textColorPair[0] + (2**8) *textColorPair[1] + (2**16)* textColorPair[2] + (2 ** 24) + textColorPair[3]);
const front = byte_to_hex_string(textColorPair.slice(4, 7));
const back = byte_to_hex_string(textColorPair.slice(7, 10));
if (text != ' ') {
const buffer = frontBuffer.get(front) || [];
buffer.push({ x, y, text });
frontBuffer.set(front, buffer);
}
const buffer = backBuffer.get(back) || [];
buffer.push({ x, y });
backBuffer.set(back, buffer);
}
}
backBuffer.forEach((buffer, back) => {
context.fillStyle = back;
buffer.forEach(value => {
context.fillRect(value.x * fontWidth, value.y * fontHeight, fontWidth, fontHeight);
});
});
frontBuffer.forEach((buffer, front) => {
context.fillStyle = front;
buffer.forEach(value => {
context.fillText(value.text, value.x * fontWidth, (value.y + 0.8) * fontHeight);
});
});
}

function byte_to_hex_string(bytes) {
const red = bytes[0].toString(16).padStart(2, '0');
const green = bytes[1].toString(16).padStart(2, '0');
const blue = bytes[2].toString(16).padStart(2, '0');
return `#${red}${green}${blue}`;
}
Loading

0 comments on commit 5db16fc

Please sign in to comment.