Skip to content

Commit

Permalink
first blood
Browse files Browse the repository at this point in the history
  • Loading branch information
whatisaphone committed Nov 3, 2019
0 parents commit 285b4f2
Show file tree
Hide file tree
Showing 13 changed files with 337 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# https://editorconfig.org/

root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.yml]
indent_size = 2
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
**/*.rs.bk
20 changes: 20 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fmt:
# Use a third-party repo since the official repo doesn't include tags.
# https://github.com/rust-lang-nursery/docker-rust-nightly/issues/3
image: instrumentisto/rust:nightly-2019-08-15
before_script:
- rustup component add rustfmt-preview
script:
- cargo fmt -- --check

check:
image: rust:1.37
before_script:
- rustup component add clippy
script:
- cargo clippy --all-targets --features strict

test:
image: rust:1.37
script:
- cargo test
24 changes: 24 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
fail_fast: true

repos:
- repo: local
hooks:
- id: fmt
name: fmt
language: system
files: '[.]rs$'
entry: rustup run nightly-2019-08-15 rustfmt

- id: check
name: check
language: system
files: '[.]rs$'
entry: cargo clippy --all-targets --features strict
pass_filenames: false

- id: test
name: test
language: system
files: '[.]rs$'
entry: cargo test
pass_filenames: false
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "genawaiter"
version = "0.0.0"
authors = ["John Simon <john@whatisaph.one>"]
edition = "2018"

[dependencies]

[features]
strict = []
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# nirvana-rust

An opinionated [Rust] project starter. There are many like it, but this one is mine.

[Rust]: https://www.rust-lang.org/

## Development

### Install prerequisites

- [Rust]
- [pre-commit]

[pre-commit]: https://pre-commit.com/

### Install the pre-commit hook

```sh
pre-commit install
```

This installs a Git hook that runs a quick sanity check before every commit.

### Run the app

```sh
cargo run
```

### Run the tests

```sh
cargo test
```
19 changes: 19 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md

edition = "2018"
error_on_line_overflow = true
error_on_unformatted = true
force_multiline_blocks = true
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true
imports_layout = "HorizontalVertical"
max_width = 88
merge_imports = true
newline_style = "Unix"
overflow_delimited_expr = true
report_fixme = "Unnumbered"
report_todo = "Unnumbered"
use_field_init_shorthand = true
version = "Two"
wrap_comments = true
59 changes: 59 additions & 0 deletions src/engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::{waker, GeneratorState};
use std::{
cell::RefCell,
future::Future,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};

type Airlock<Y> = Rc<RefCell<Option<Y>>>;

pub fn advance<Y, R>(
future: Pin<&mut impl Future<Output = R>>,
airlock: &Airlock<Y>,
) -> GeneratorState<Y, R> {
let waker = waker::create();
let mut cx = Context::from_waker(&waker);

match future.poll(&mut cx) {
Poll::Pending => {
let value = airlock.borrow_mut().take().unwrap();
GeneratorState::Yielded(value)
}
Poll::Ready(value) => GeneratorState::Complete(value),
}
}

pub struct Co<Y> {
pub(crate) airlock: Airlock<Y>,
}

impl<Y> Co<Y> {
pub fn yield_(&self, value: Y) -> impl Future<Output = ()> + '_ {
*self.airlock.borrow_mut() = Some(value);
Barrier {
airlock: &self.airlock,
}
}
}

pub struct Barrier<'y, Y> {
airlock: &'y Airlock<Y>,
}

impl<'y, Y> Future for Barrier<'y, Y> {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.airlock.borrow().is_none() {
// If there is no value in the airlock, resume the generator so it produces
// one.
Poll::Ready(())
} else {
// If there is a value, pause the generator so we can yield the value to the
// caller.
Poll::Pending
}
}
}
16 changes: 16 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#![feature(async_await, async_closure)]
#![warn(future_incompatible, rust_2018_compatibility, rust_2018_idioms, unused)]
#![warn(clippy::pedantic)]
// #![warn(clippy::cargo)]
#![cfg_attr(feature = "strict", deny(warnings))]

pub use crate::{
engine::Co,
safe_rc::Generator as SafeRcGenerator,
state::GeneratorState,
};

mod engine;
mod safe_rc;
mod state;
mod waker;
111 changes: 111 additions & 0 deletions src/safe_rc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use crate::{engine::advance, Co, GeneratorState};
use std::{cell::RefCell, future::Future, pin::Pin, rc::Rc};

pub struct Generator<Y, F: Future> {
airlock: Rc<RefCell<Option<Y>>>,
future: Pin<Box<F>>,
}

impl<Y, F: Future> Generator<Y, F> {
pub fn new(start: impl FnOnce(Co<Y>) -> F) -> Self {
let airlock = Rc::new(RefCell::new(None));
let future = {
let airlock = airlock.clone();
Box::pin(start(Co { airlock }))
};
Self { airlock, future }
}

pub fn resume(&mut self) -> GeneratorState<Y, F::Output> {
advance(self.future.as_mut(), &self.airlock)
}
}

#[cfg(test)]
mod tests {
use crate::{safe_rc::Generator, Co, GeneratorState};
use std::future::Future;

async fn simple_producer(c: Co<i32>) -> &'static str {
c.yield_(10).await;
"done"
}

#[test]
fn function() {
let mut gen = Generator::new(simple_producer);
assert_eq!(gen.resume(), GeneratorState::Yielded(10));
assert_eq!(gen.resume(), GeneratorState::Complete("done"));
}

#[test]
fn simple_closure() {
async fn gen(i: i32, co: Co<i32>) -> &'static str {
co.yield_(i * 2).await;
"done"
}

let mut gen = Generator::new(|co| gen(5, co));
assert_eq!(gen.resume(), GeneratorState::Yielded(10));
assert_eq!(gen.resume(), GeneratorState::Complete("done"));
}

#[test]
fn async_closure() {
let mut gen = Generator::new(async move |co| {
co.yield_(10).await;
"done"
});
assert_eq!(gen.resume(), GeneratorState::Yielded(10));
assert_eq!(gen.resume(), GeneratorState::Complete("done"));
}

#[test]
fn pinned() {
#[inline(never)]
async fn produce(addrs: &mut Vec<*const i32>, co: Co<i32>) -> &'static str {
use std::cell::Cell;

let sentinel: Cell<i32> = Cell::new(0x8001);
let sentinel_ref: &Cell<i32> = &sentinel;

assert_eq!(sentinel.get(), 0x8001);
sentinel_ref.set(0x8002);
assert_eq!(sentinel.get(), 0x8002);
addrs.push(sentinel.as_ptr());

co.yield_(10).await;

assert_eq!(sentinel.get(), 0x8002);
sentinel_ref.set(0x8003);
assert_eq!(sentinel.get(), 0x8003);
addrs.push(sentinel.as_ptr());

co.yield_(20).await;

assert_eq!(sentinel.get(), 0x8003);
sentinel_ref.set(0x8004);
assert_eq!(sentinel.get(), 0x8004);
addrs.push(sentinel.as_ptr());

"done"
}

fn create_generator(
addrs: &mut Vec<*const i32>,
) -> Generator<i32, impl Future<Output = &'static str> + '_> {
let mut gen = Generator::new(move |co| produce(addrs, co));
assert_eq!(gen.resume(), GeneratorState::Yielded(10));
gen
}

let mut addrs = Vec::new();
let mut gen = create_generator(&mut addrs);

assert_eq!(gen.resume(), GeneratorState::Yielded(20));
assert_eq!(gen.resume(), GeneratorState::Complete("done"));
drop(gen);

assert!(addrs.iter().all(|&p| p == addrs[0]));
}
}
6 changes: 6 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[cfg_attr(test, derive(PartialEq, Debug))]
#[allow(clippy::module_name_repetitions)]
pub enum GeneratorState<Y, R> {
Yielded(Y),
Complete(R),
}
17 changes: 17 additions & 0 deletions src/waker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::{
ptr,
task::{RawWaker, RawWakerVTable, Waker},
};

pub fn create() -> Waker {
unsafe { Waker::from_raw(RAW_WAKER) }
}

const VTABLE: RawWakerVTable = RawWakerVTable::new(
/* clone */ |_| panic!(),
/* wake */ |_| {},
/* wake_by_ref */ |_| {},
/* drop */ |_| {},
);

const RAW_WAKER: RawWaker = RawWaker::new(ptr::null(), &VTABLE);

0 comments on commit 285b4f2

Please sign in to comment.