Skip to content

Commit

Permalink
feat: scaffold for ffi io
Browse files Browse the repository at this point in the history
  • Loading branch information
mosure committed Jul 30, 2024
1 parent c19b573 commit 6f5a488
Show file tree
Hide file tree
Showing 10 changed files with 545 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/clippy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ jobs:
enable-sccache: "true"

- name: lint
run: cargo clippy -- -Dwarnings
run: cargo clippy --all -- -Dwarnings
18 changes: 11 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ exclude = [
default-run = "viewer"


[workspace]
members = [
".",
"ffi/bevy_zeroverse",
]


[features]
default = [
"asset_pipeline",
Expand Down Expand Up @@ -71,14 +78,18 @@ bevy_args = "1.6"
bevy-inspector-egui = { version = "0.25", optional = true }
bevy_panorbit_camera = { version = "0.19", optional = true, features = ["bevy_egui"] }
clap = { version = "4.4", features = ["derive"] }
futures-intrusive = "0.5"
glob = "0.3"
itertools = "0.13"
noise = { version = "0.9" }
pollster = "0.3"
pyo3 = { version = "0.22", features = ["extension-module"] }
rand = "0.8"
rayon = { version = "1.10", optional = true }
serde = "1.0"
strum = "0.26"
strum_macros = "0.26"
wgpu = "0.20.0"


[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand Down Expand Up @@ -145,10 +156,3 @@ path = "src/lib.rs"
name = "viewer"
path = "src/viewer.rs"
required-features = ["viewer"]


[workspace]
members = [
"ffi/bevy_zeroverse",
".",
]
2 changes: 2 additions & 0 deletions ffi/bevy_zeroverse/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ edition = "2021"

[dependencies]
bevy_zeroverse = { path = "../../" }
ndarray = { version = "0.15", features = ["blas"] }
once_cell = "1.19"
pyo3 = { version = "0.22", features = ["extension-module"] }
pyo3-log = "0.11"

Expand Down
21 changes: 7 additions & 14 deletions ffi/bevy_zeroverse/python/dataloader.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import bevy_zeroverse


print('initializing bevy zeroverse...')
bevy_zeroverse.initialize()
print('bevy zeroverse initialized!')
config = bevy_zeroverse.BevyZeroverseConfig()

config.headless = True
config.num_cameras = 4

# dataloader = bevy_zeroverse.ZeroverseDataloader(
# width=256,
# height=144,
# num_cameras=4,
# render_modes=['color', 'depth', 'normal'],
# seed=0,
# scene_type='room',
# )
bevy_zeroverse.initialize(config)

# for batch in dataloader:
# print(batch)
# break

sample = bevy_zeroverse.next()
print(sample)
127 changes: 120 additions & 7 deletions ffi/bevy_zeroverse/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,109 @@
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
Mutex,
atomic::{
AtomicBool,
Ordering,
},
mpsc::{
Sender,
Receiver,
RecvTimeoutError,
},
},
thread,
time::Duration,
};

// use bevy::prelude::*;
use pyo3::prelude::*;
use bevy::prelude::*;
use ndarray::{Array2, Array3};
use once_cell::sync::OnceCell;
use pyo3::{
prelude::*,
exceptions::PyTimeoutError,
types::PyList,
};

use ::bevy_zeroverse::app::{
viewer_app,
BevyZeroverseConfig,
};


type ColorImage = Array3<u8>;
type DepthImage = Array2<f32>;
type NormalImage = Array2<[f32; 3]>;


#[derive(Clone)]
#[pyclass]
struct View {
color: ColorImage,
depth: DepthImage,
normal: NormalImage,

#[pyo3(get, set)]
view_from_world: [[f32; 4]; 4],

#[pyo3(get, set)]
fovy: f32,
}

#[pymethods]
impl View {
// TODO: upgrade rust-numpy to latest once pyo3 0.22 support is available
#[getter]
fn color<'py>(&self, py: Python<'py>) -> Bound<'py, PyList> {
let color_list: Vec<_> = self.color.iter().map(|&v| v.into_py(py)).collect();
PyList::new_bound(py, color_list)
}

#[getter]
fn depth<'py>(&self, py: Python<'py>) -> Bound<'py, PyList> {
let depth_list: Vec<_> = self.depth.iter().map(|&v| v.into_py(py)).collect();
PyList::new_bound(py, depth_list)
}

#[getter]
fn normal<'py>(&self, py: Python<'py>) -> Bound<'py, PyList> {
let normal_list: Vec<_> = self.normal.iter().map(|&v| v.into_py(py)).collect();
PyList::new_bound(py, normal_list)
}
}

use ::bevy_zeroverse::app::viewer_app;
#[pyclass]
struct Sample {
views: Vec<View>,
}

#[pymethods]
impl Sample {
#[getter]
fn views<'py>(&self, py: Python<'py>) -> Bound<'py, PyList> {
let views_list: Vec<_> = self.views.iter().map(|v| v.clone().into_py(py)).collect();
PyList::new_bound(py, views_list)
}
}

static SAMPLE_RECEIVER: OnceCell<Arc<Mutex<Receiver<Sample>>>> = OnceCell::new();
// static SAMPLE_SENDER: OnceCell<Sender<Sample>> = OnceCell::new();


// TODO: create Dataloader torch class (or a render `n` frames and return capture fn, used within a python wrapper dataloader, wrapper requires setup.py to include the python module)


pub fn setup_and_run_app(
new_thread: bool,
override_args: Option<BevyZeroverseConfig>,
) {
let ready = Arc::new(AtomicBool::new(false));

let startup = {
let ready = Arc::clone(&ready);

move || {
let mut app = viewer_app(None);
let mut app = viewer_app(override_args);

ready.store(true, Ordering::Release);

Expand All @@ -33,6 +112,8 @@ pub fn setup_and_run_app(
};

if new_thread {
info!("starting bevy_zeroverse in a new thread");

thread::spawn(startup);

while !ready.load(Ordering::Acquire) {
Expand All @@ -44,21 +125,53 @@ pub fn setup_and_run_app(
}


// TODO: add BevyZeroverseViewer struct parameter
#[pyfunction]
#[pyo3(signature = (override_args=None))]
fn initialize(
py: Python<'_>,
override_args: Option<BevyZeroverseConfig>,
) {
py.allow_threads(|| {
setup_and_run_app(true);
setup_and_run_app(true, override_args);
});
}


// TODO: support batch dimension (e.g. single array allocation for multiple samples)
// TODO: add systems for pushing and receiving camera outputs + metadata to python
// TODO: add options to bevy_zeroverse.next (e.g. render_modes, scene parameters, etc.)
#[pyfunction]
fn next() -> PyResult<Sample> {
// TODO: advance 'n' frames - requires app reference
// TODO: capture frame - requires app system registration to write to textures and readback, triggered by app event after 'n' frames
// TODO: send frame

let receiver = SAMPLE_RECEIVER.get().unwrap();
let receiver = receiver.lock().unwrap();

let timeout = Duration::from_secs(5);

match receiver.recv_timeout(timeout) {
Ok(sample) => Ok(sample),
Err(RecvTimeoutError::Timeout) => {
Err(PyTimeoutError::new_err("receive operation timed out"))
}
Err(RecvTimeoutError::Disconnected) => {
Err(PyTimeoutError::new_err("channel disconnected"))
}
}
}


#[pymodule]
fn bevy_zeroverse(m: &Bound<'_, PyModule>) -> PyResult<()> {
pyo3_log::init();

m.add_class::<BevyZeroverseConfig>()?;
m.add_class::<Sample>()?;
m.add_class::<View>()?;

m.add_function(wrap_pyfunction!(initialize, m)?)?;
m.add_function(wrap_pyfunction!(next, m)?)?;
Ok(())
}
Loading

0 comments on commit 6f5a488

Please sign in to comment.