├── .cargo └── config ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── index.html ├── src ├── core.rs ├── fetch.rs ├── interval.rs ├── lib.rs └── timeout.rs └── stdweb-io-html ├── Cargo.toml └── src └── main.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.asmjs-unknown-emscripten] 2 | rustflags = [ 3 | "-C", "link-arg=-s", 4 | "-C", "link-arg=MODULARIZE=1", 5 | "-C", "link-arg=-s", 6 | "-C", "link-arg=EXPORT_NAME='WebIO'", 7 | "-C", "link-arg=-Oz", 8 | "-C", "link-arg=-s", 9 | "-C", "link-arg=TOTAL_MEMORY=33554432", 10 | "-C", "link-arg=-s", 11 | "-C", "link-arg=ALLOW_MEMORY_GROWTH=1", 12 | ] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stdweb-io" 3 | version = "0.1.0" 4 | authors = ["Christopher Serr "] 5 | 6 | [dependencies] 7 | futures = "0.1.16" 8 | http = "0.1.1" 9 | stdweb = "0.1.3" 10 | 11 | [workspace] 12 | members = ["stdweb-io-html"] 13 | 14 | [profile.release] 15 | lto = true 16 | panic = "abort" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Christopher Serr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | asmjs: 2 | @cargo rustc --release -p stdweb-io-html --target asmjs-unknown-emscripten -- -C opt-level=z 3 | @cp target/asmjs-unknown-emscripten/release/stdweb-io-html*.js* stdweb-io.js 4 | 5 | debug: 6 | @cargo build -p stdweb-io-html --target asmjs-unknown-emscripten 7 | @cp target/asmjs-unknown-emscripten/debug/stdweb-io-html*.js* stdweb-io.js 8 | 9 | run: 10 | @python -m SimpleHTTPServer 8080 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stdweb-io 2 | Tokio / Hyper like IO for the Browser based on the stdweb, futures and http crates. 3 | 4 | ## Deprecation 5 | 6 | This repository is mostly meant for experimentation. A lot of this is currently being merged into stdweb itself. The HTTP implementation most likely won't land in there, so it may still make sense to have a crates that has that implementation. 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | use futures::future::{lazy, ExecuteError, Executor, IntoFuture}; 2 | use futures::executor::{self, Notify, Spawn}; 3 | use futures::Async; 4 | use std::result::Result as StdResult; 5 | use std::cell::{Cell, RefCell}; 6 | use {defer, Future}; 7 | 8 | static CORE: &Core = &Core; 9 | 10 | struct Core; 11 | 12 | pub fn spawn(future: F) 13 | where 14 | F: Future + 'static, 15 | { 16 | CORE.execute(future).ok(); 17 | } 18 | 19 | pub fn spawn_fn(func: F) 20 | where 21 | F: FnOnce() -> R + 'static, 22 | R: IntoFuture + 'static, 23 | { 24 | spawn(lazy(|| func())); 25 | } 26 | 27 | pub fn spawn_deferred(future: F) 28 | where 29 | F: Future + 'static, 30 | { 31 | spawn(defer().then(|_| future)); 32 | } 33 | 34 | pub fn spawn_deferred_fn(func: F) 35 | where 36 | F: FnOnce() -> R + 'static, 37 | R: IntoFuture + 'static, 38 | { 39 | spawn(defer().then(|_| func())); 40 | } 41 | 42 | fn decrement_ref_count(id: usize) { 43 | let count = { 44 | let spawned_ptr = id as *const SpawnedTask; 45 | let spawned = unsafe { &*spawned_ptr }; 46 | let mut count = spawned.ref_count.get(); 47 | count -= 1; 48 | spawned.ref_count.set(count); 49 | // println!("Drop {}", count); 50 | count 51 | }; 52 | if count == 0 { 53 | let spawned_ptr = id as *mut SpawnedTask; 54 | unsafe { Box::from_raw(spawned_ptr) }; 55 | } 56 | } 57 | 58 | struct SpawnedTask { 59 | ref_count: Cell, 60 | spawn: RefCell + 'static>>>, 61 | } 62 | 63 | fn execute_spawn(spawned_ptr: *const SpawnedTask) { 64 | let spawned = unsafe { &*spawned_ptr }; 65 | 66 | // This is probably suboptimal, as a resubmission of the same Task while it 67 | // is being executed results in a panic. It is not entirely clear if a Task 68 | // is allowed to do that, but I would expect that this is valid behavior, as 69 | // the notification could happen while the Task is still executing, in a 70 | // truly multi-threaded situation. So we probably have to deal with it here 71 | // at some point too. This already happened in the IntervalStream, so that 72 | // should be cleaned up then as well then. The easiest solution is to try to 73 | // lock it instead and if it fails, increment a counter. The one that 74 | // initially blocked the RefCell then just reexecutes the Task until the 75 | // Task is finished or the counter reaches 0. 76 | 77 | if spawned 78 | .spawn 79 | .borrow_mut() 80 | .poll_future_notify(&CORE, spawned_ptr as usize) != Ok(Async::NotReady) 81 | { 82 | decrement_ref_count(spawned_ptr as usize); 83 | } 84 | } 85 | 86 | impl Executor for Core 87 | where 88 | F: Future + 'static, 89 | { 90 | fn execute(&self, future: F) -> StdResult<(), ExecuteError> { 91 | // println!("Execute"); 92 | 93 | let spawned_ptr = Box::into_raw(Box::new(SpawnedTask { 94 | ref_count: Cell::new(1), 95 | spawn: RefCell::new(executor::spawn(Box::new(future.fuse()) 96 | as Box + 'static>)), 97 | })); 98 | 99 | execute_spawn(spawned_ptr); 100 | 101 | // println!("Execute End"); 102 | 103 | Ok(()) 104 | } 105 | } 106 | 107 | impl Notify for Core { 108 | fn notify(&self, spawned_id: usize) { 109 | // println!("Notify"); 110 | 111 | let spawned_ptr = spawned_id as *const SpawnedTask; 112 | 113 | execute_spawn(spawned_ptr); 114 | 115 | // println!("Notify End"); 116 | } 117 | 118 | fn clone_id(&self, id: usize) -> usize { 119 | let spawned_ptr = id as *const SpawnedTask; 120 | let spawned = unsafe { &*spawned_ptr }; 121 | let mut count = spawned.ref_count.get(); 122 | count += 1; 123 | spawned.ref_count.set(count); 124 | // println!("Clone {}", count); 125 | id 126 | } 127 | 128 | fn drop_id(&self, id: usize) { 129 | decrement_ref_count(id); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/fetch.rs: -------------------------------------------------------------------------------- 1 | use futures::unsync::oneshot::{channel, Receiver}; 2 | use futures::{Async, Future, Poll}; 3 | use {stdweb, Error, Request, Response}; 4 | use std::ops::{Deref, DerefMut}; 5 | use std::slice; 6 | 7 | pub struct FetchFuture(Receiver, Error>>); 8 | 9 | impl Future for FetchFuture { 10 | type Item = Response; 11 | type Error = Error; 12 | 13 | fn poll(&mut self) -> Poll { 14 | let async = self.0.poll().expect("Unexpected cancel"); 15 | match async { 16 | Async::Ready(Ok(v)) => Ok(Async::Ready(v)), 17 | Async::Ready(Err(e)) => Err(e), 18 | Async::NotReady => Ok(Async::NotReady), 19 | } 20 | } 21 | } 22 | 23 | pub fn fetch(request: Request) -> FetchFuture { 24 | stdweb::initialize(); 25 | 26 | let (tx, rx) = channel(); 27 | let mut tx = Some(tx); 28 | 29 | let (parts, body) = request.into_parts(); 30 | let uri = parts.uri.to_string(); 31 | 32 | let resolve_ok = 33 | move |okay: bool, status_code: u16, headers: Vec>, response_ref: f64| { 34 | let body = AsyncBody { response_ref }; 35 | 36 | let mut builder = Response::builder(); 37 | 38 | for header in &headers { 39 | builder.header(header[0].as_str(), header[1].as_str()); 40 | } 41 | 42 | let response = builder.status(status_code).body(body); 43 | 44 | tx.take() 45 | .expect("Unexpected second resolve of fetch") 46 | .send(response) 47 | .ok(); 48 | }; 49 | 50 | let resolve_err = move || -> () { 51 | unimplemented!() 52 | // tx.take() 53 | // .expect("Unexpected second resolve of fetch") 54 | // .send(Ok(())) 55 | // .ok(); 56 | }; 57 | 58 | js! { 59 | const uri = @{uri}; 60 | const resolveOk = @{resolve_ok}; 61 | const resolveErr = @{resolve_err}; 62 | 63 | fetch(uri).then( 64 | function(res) { 65 | // console.log(res); 66 | const okay = res.ok; 67 | const status = res.status; 68 | const headers = Array.from(res.headers.entries()); 69 | const responseRef = Module.STDWEB.acquire_rust_reference(res); 70 | resolveOk(okay, status, headers, responseRef); 71 | resolveOk.drop(); 72 | resolveErr.drop(); 73 | }, 74 | function(err) { 75 | resolveErr(); 76 | resolveOk.drop(); 77 | resolveErr.drop(); 78 | } 79 | ); 80 | } 81 | 82 | FetchFuture(rx) 83 | } 84 | 85 | pub struct BodyData(*mut [u8]); 86 | 87 | impl Drop for BodyData { 88 | fn drop(&mut self) { 89 | let ptr: f64 = self.as_ptr() as usize as f64; 90 | js! { 91 | _free(@{ptr}); 92 | } 93 | } 94 | } 95 | 96 | impl Deref for BodyData { 97 | type Target = [u8]; 98 | fn deref(&self) -> &[u8] { 99 | unsafe { &*self.0 } 100 | } 101 | } 102 | 103 | impl DerefMut for BodyData { 104 | fn deref_mut(&mut self) -> &mut [u8] { 105 | unsafe { &mut *self.0 } 106 | } 107 | } 108 | 109 | #[derive(Debug)] 110 | pub struct AsyncBody { 111 | response_ref: f64, 112 | } 113 | 114 | impl Drop for AsyncBody { 115 | fn drop(&mut self) { 116 | let response_ref = self.response_ref; 117 | js! { 118 | const responseRef = @{response_ref}; 119 | Module.STDWEB.decrement_refcount(responseRef); 120 | } 121 | } 122 | } 123 | 124 | impl AsyncBody { 125 | pub fn get(self) -> BodyFuture { 126 | let (tx, rx) = channel(); 127 | let mut tx = Some(tx); 128 | 129 | let resolve = move |ptr: f64, len: f64| { 130 | let buf = unsafe { slice::from_raw_parts_mut(ptr as usize as *mut u8, len as usize) }; 131 | let buf = BodyData(buf); 132 | tx.take() 133 | .expect("Unexpected second resolve of body") 134 | .send(buf) 135 | .ok(); 136 | }; 137 | 138 | let response_ref = self.response_ref; 139 | 140 | js! { 141 | const resolve = @{resolve}; 142 | const responseRef = @{response_ref}; 143 | const response = Module.STDWEB.acquire_js_reference(responseRef); 144 | response.arrayBuffer().then( 145 | function(arrayBuffer) { 146 | const len = arrayBuffer.byteLength; 147 | const ptr = _malloc(len); 148 | writeArrayToMemory(new Int8Array(arrayBuffer), ptr); 149 | resolve(ptr, len); 150 | resolve.drop(); 151 | }, 152 | function(error) { 153 | resolve.drop(); 154 | } 155 | ); 156 | } 157 | 158 | BodyFuture(rx) 159 | } 160 | } 161 | 162 | pub struct BodyFuture(Receiver); 163 | 164 | impl Future for BodyFuture { 165 | type Item = BodyData; 166 | type Error = (); 167 | 168 | fn poll(&mut self) -> Poll { 169 | self.0.poll().map_err(|_| ()) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/interval.rs: -------------------------------------------------------------------------------- 1 | use futures::unsync::mpsc::{unbounded, UnboundedReceiver}; 2 | use futures::{Poll, Stream}; 3 | use stdweb; 4 | use std::time::Duration; 5 | use stdweb::unstable::TryInto; 6 | use std::mem::ManuallyDrop; 7 | 8 | pub struct IntervalStream { 9 | receiver: ManuallyDrop>, 10 | interval_ref: f64, 11 | } 12 | 13 | impl Drop for IntervalStream { 14 | fn drop(&mut self) { 15 | unsafe { 16 | // Drop the Receiver before Sender. The Sender would otherwise try 17 | // to resubmit the Task to the Executor, which at least at the 18 | // moment causes a panic, as the Executor might be dropping this 19 | // Stream while it is already executing the Task, so that would be a 20 | // concurrent execution of the same Task, which the RefCell in our 21 | // Executor prevents by panicking. 22 | ManuallyDrop::drop(&mut self.receiver); 23 | } 24 | let interval_ref = self.interval_ref; 25 | js! { 26 | const intervalRef = @{interval_ref}; 27 | const intervalInfo = Module.STDWEB.acquire_js_reference(intervalRef); 28 | clearInterval(intervalInfo.id); 29 | intervalInfo.resolve.drop(); 30 | Module.STDWEB.decrement_refcount(intervalRef); 31 | } 32 | } 33 | } 34 | 35 | impl Stream for IntervalStream { 36 | type Item = (); 37 | type Error = (); 38 | 39 | fn poll(&mut self) -> Poll, Self::Error> { 40 | Ok(self.receiver.poll().expect("Unexpected cancel")) 41 | } 42 | } 43 | 44 | pub fn interval(duration: Duration) -> IntervalStream { 45 | stdweb::initialize(); 46 | let milli = duration.as_secs() as f64 * 1_000.0 + duration.subsec_nanos() as f64 / 1_000_000.0; 47 | let (tx, rx) = unbounded(); 48 | 49 | let resolve = move || { 50 | tx.unbounded_send(()).ok(); 51 | }; 52 | 53 | let interval_ref = js! { 54 | const resolve = @{resolve}; 55 | const milli = @{milli}; 56 | 57 | const intervalInfo = { 58 | id: null, 59 | resolve: resolve, 60 | }; 61 | 62 | intervalInfo.id = setInterval( 63 | function() { 64 | intervalInfo.resolve(); 65 | }, 66 | milli 67 | ); 68 | 69 | return Module.STDWEB.acquire_rust_reference(intervalInfo); 70 | }.try_into() 71 | .expect("Expected Reference to Interval Stream"); 72 | 73 | IntervalStream { 74 | receiver: ManuallyDrop::new(rx), 75 | interval_ref, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | 3 | pub extern crate futures; 4 | pub extern crate http; 5 | #[macro_use] 6 | pub extern crate stdweb; 7 | 8 | pub use http::{Error, Request, Response, Result}; 9 | pub use futures::{Future, Stream}; 10 | 11 | mod core; 12 | mod fetch; 13 | mod timeout; 14 | mod interval; 15 | 16 | pub use core::{spawn, spawn_deferred, spawn_deferred_fn, spawn_fn}; 17 | pub use fetch::{fetch, AsyncBody, BodyData, BodyFuture, FetchFuture}; 18 | pub use timeout::{defer, timeout, TimeoutFuture}; 19 | pub use interval::{interval, IntervalStream}; 20 | -------------------------------------------------------------------------------- /src/timeout.rs: -------------------------------------------------------------------------------- 1 | use futures::unsync::oneshot::{channel, Receiver}; 2 | use futures::{Future, Poll}; 3 | use stdweb; 4 | use std::time::Duration; 5 | 6 | pub struct TimeoutFuture(Receiver<()>); 7 | 8 | impl Future for TimeoutFuture { 9 | type Item = (); 10 | type Error = (); 11 | 12 | fn poll(&mut self) -> Poll { 13 | Ok(self.0.poll().expect("Unexpected cancel")) 14 | } 15 | } 16 | 17 | pub fn timeout(duration: Duration) -> TimeoutFuture { 18 | stdweb::initialize(); 19 | 20 | let milli = duration.as_secs() as f64 * 1_000.0 + duration.subsec_nanos() as f64 / 1_000_000.0; 21 | let (tx, rx) = channel(); 22 | 23 | let mut tx = Some(tx); 24 | let resolve = move || { 25 | tx.take() 26 | .expect("Unexpected second resolve of setTimeout") 27 | .send(()) 28 | .ok(); 29 | }; 30 | 31 | js! { 32 | const milli = @{milli}; 33 | const resolve = @{resolve}; 34 | 35 | setTimeout( 36 | function() { 37 | resolve(); 38 | resolve.drop(); 39 | }, 40 | milli 41 | ); 42 | } 43 | 44 | TimeoutFuture(rx) 45 | } 46 | 47 | pub fn defer() -> TimeoutFuture { 48 | timeout(Duration::from_secs(0)) 49 | } 50 | -------------------------------------------------------------------------------- /stdweb-io-html/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stdweb-io-html" 3 | version = "0.1.0" 4 | authors = ["Christopher Serr "] 5 | 6 | [dependencies] 7 | futures-await = { git = 'https://github.com/alexcrichton/futures-await' } 8 | stdweb-io = { path = ".." } 9 | serde = "*" 10 | serde_json = "*" 11 | serde_derive = "*" 12 | -------------------------------------------------------------------------------- /stdweb-io-html/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro, conservative_impl_trait, generators)] 2 | #![no_main] 3 | 4 | extern crate futures_await as futures; 5 | extern crate serde; 6 | #[macro_use] 7 | extern crate serde_derive; 8 | extern crate serde_json; 9 | extern crate stdweb_io; 10 | 11 | use futures::prelude::*; 12 | 13 | use stdweb_io::{fetch, interval, spawn, timeout}; 14 | use stdweb_io::http::Request; 15 | use std::time::Duration; 16 | 17 | #[derive(Deserialize, Debug)] 18 | struct Games { 19 | games: Vec, 20 | } 21 | 22 | #[derive(Deserialize, Debug)] 23 | struct Game { 24 | name: String, 25 | categories: Vec, 26 | } 27 | 28 | #[derive(Deserialize, Debug)] 29 | struct Category { 30 | name: String, 31 | } 32 | 33 | #[no_mangle] 34 | pub extern "C" fn do_stuff() { 35 | let f = async_block! { 36 | println!("Waiting 5 seconds"); 37 | 38 | await!(timeout(Duration::from_secs(5)))?; 39 | 40 | println!("5 seconds done"); 41 | 42 | let progress_report = async_block! { 43 | let mut counter = 0; 44 | 45 | #[async] 46 | for _ in interval(Duration::from_millis(100)) { 47 | counter += 100; 48 | println!("{}ms progressed", counter); 49 | } 50 | 51 | Ok(()) 52 | }; 53 | 54 | let request = async_block! { 55 | println!("Starting Request"); 56 | 57 | let response = await!(fetch( 58 | Request::get("https://splits.io/api/v3/games?search=sonic") 59 | .body(()) 60 | .unwrap(), 61 | ).map_err(|_| ()))?; 62 | 63 | println!("Request finished: {:#?}", response); 64 | 65 | let (_, body) = response.into_parts(); 66 | 67 | let body = await!(body.get())?; 68 | 69 | let games: Games = serde_json::from_slice(&body).unwrap(); 70 | 71 | println!("Games: {:#?}", games); 72 | 73 | Ok::<(), ()>(()) 74 | }; 75 | 76 | await!(progress_report.select(request).map(|_| ()).map_err(|_| ()))?; 77 | 78 | Ok(()) 79 | }; 80 | 81 | spawn(f); 82 | } 83 | --------------------------------------------------------------------------------