Refactor editor world state

This commit is contained in:
mars 2022-10-22 11:11:08 -06:00
parent 4bfd6977c3
commit 1834b77522
5 changed files with 316 additions and 236 deletions

View File

@ -7,7 +7,6 @@ use egui_winit::winit::{
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
use legion::EntityStore;
use parking_lot::RwLock;
use std::sync::Arc;
@ -16,6 +15,7 @@ mod model;
mod render;
mod script;
mod ui;
mod world;
struct Application {
window: winit::window::Window,
@ -29,9 +29,9 @@ struct Application {
egui_rp: EguiRenderPass,
ui: ui::UserInterface,
viewport: ui::ViewportWidget,
render_state: Arc<RwLock<render::RenderState>>,
world: Arc<RwLock<world::World>>,
render_state: render::RenderState,
file_receiver: crossbeam_channel::Receiver<ui::FileEvent>,
objects: Vec<ui::ObjectWidget>,
}
impl Application {
@ -73,9 +73,18 @@ impl Application {
let egui_state = egui_winit::State::new(4096, &window);
let egui_ctx = egui::Context::default();
let mut egui_rp = egui_wgpu_backend::RenderPass::new(&device, config.format, 1);
let render_state =
render::RenderState::new(device.clone(), queue.clone(), config.format, &mut egui_rp);
let viewport_texture = render_state
let mut world = world::World::new();
let render_state = render::RenderState::new(
&mut world,
device.clone(),
queue.clone(),
config.format,
&mut egui_rp,
);
let viewport_texture = world
.resources
.get::<render::ViewportStore>()
.unwrap()
@ -94,11 +103,11 @@ impl Application {
egui_state,
egui_ctx,
egui_rp,
world: Arc::new(RwLock::new(world)),
ui: ui::UserInterface::new(file_sender),
viewport: ui::ViewportWidget::new(viewport_texture),
render_state: Arc::new(RwLock::new(render_state)),
render_state,
file_receiver,
objects: vec![],
}
}
@ -115,14 +124,13 @@ impl Application {
ui::ImportKind::Gltf => import::load_gltf(path),
};
let mut render_state = self.render_state.write();
let loaded = render_state.load_model(&model);
let mut world = self.world.write();
let loaded = world.load_model(&model);
let transform = glam::Mat4::IDENTITY;
let widgets = render_state.spawn_model(&loaded, &transform);
self.objects.extend(widgets.into_iter());
},
world.spawn_model(&loaded, &transform);
}
Ok(ui::FileEvent::LoadScript(path)) => {
script::Script::new(&path, self.render_state.to_owned());
script::Script::new(&path, self.world.to_owned());
}
Err(crossbeam_channel::TryRecvError::Empty) => break,
Err(crossbeam_channel::TryRecvError::Disconnected) => {
@ -130,22 +138,9 @@ impl Application {
}
}
}
let mut render_state = self.render_state.write();
for object in self.objects.iter_mut() {
object.flush_dirty(|object| {
let transform = object.transform.to_mat4();
for entity in object.entities.iter() {
render_state
.world
.entry_mut(*entity)
.unwrap()
.get_component_mut::<cyborg::scene::Transform>()
.unwrap()
.transform = transform;
}
});
}
let mut world = self.world.write();
world.flush_dirty_objects();
}
pub fn on_resize(&mut self, new_size: PhysicalSize<u32>) {
@ -168,15 +163,22 @@ impl Application {
puffin::profile_scope!("Draw egui");
let raw_input = self.egui_state.take_egui_input(&self.window);
self.egui_ctx.run(raw_input, |ctx| {
self.ui.run(ctx, &mut self.viewport, &mut self.objects)
let mut world = self.world.write();
let mut resources = ui::UiResources {
world: &mut world,
viewport: &mut self.viewport,
};
self.ui.run(ctx, &mut resources);
})
};
{
puffin::profile_scope!("Main render");
let mut render_state = self.render_state.write();
render_state.update_viewport(&mut self.egui_rp, &mut self.viewport);
render_state.render();
let mut world = self.world.write();
world.update_viewport(&mut self.egui_rp, &mut self.viewport);
world.execute(&mut self.render_state.render_schedule);
}
puffin::profile_scope!("Render egui");

View File

@ -1,6 +1,7 @@
use crate::model;
use crate::ui::{ObjectWidget, ViewportWidget};
use crate::ui::ViewportWidget;
use crate::wgpu;
use crate::world::World;
use cyborg::camera::Camera;
use cyborg::storage::mesh::MeshHandle;
use cyborg::viewport::{Viewport, ViewportInfo, ViewportViews};
@ -154,13 +155,12 @@ impl cyborg::legion::RenderCallbacks for ViewportStore {
}
pub struct RenderState {
pub world: legion::World,
pub resources: legion::Resources,
pub render_schedule: legion::Schedule,
}
impl RenderState {
pub fn new(
world: &mut World,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
output_format: wgpu::TextureFormat,
@ -169,8 +169,8 @@ impl RenderState {
use cyborg::scene::{DebugDrawList, DebugVertex};
use cyborg::shader::{ShaderStore, ShaderWatcher};
let mut world = legion::World::default();
let mut resources = legion::Resources::default();
let resources = &mut world.resources;
let viewport_store =
ViewportStore::new(device.clone(), queue.clone(), output_format, render_pass);
@ -200,7 +200,7 @@ impl RenderState {
resources.insert(mesh_shaders);
let mut render_schedule = legion::Schedule::builder();
cyborg::legion::build_renderer(viewport_store, &mut resources, &mut render_schedule);
cyborg::legion::build_renderer(viewport_store, resources, &mut render_schedule);
let render_schedule = render_schedule.build();
// make debug draw grid
@ -242,13 +242,11 @@ impl RenderState {
},
));
Self {
world,
resources,
render_schedule,
}
Self { render_schedule }
}
}
impl World {
pub fn update_viewport(
&mut self,
egui_rp: &mut egui_wgpu_backend::RenderPass,
@ -277,11 +275,6 @@ impl RenderState {
}
}
pub fn render(&mut self) {
self.render_schedule
.execute(&mut self.world, &mut self.resources);
}
pub fn load_model(&mut self, model: &model::Model) -> model::LoadedModel {
model::LoadedModel {
name: model.name.clone(),
@ -339,49 +332,4 @@ impl RenderState {
mesh_pass.get_mesh_pool().load(mesh).unwrap()
}
pub fn spawn_model(
&mut self,
model: &model::LoadedModel,
transform: &glam::Mat4,
) -> Vec<ObjectWidget> {
// TODO use model name?
let mut objects = Vec::new();
for object in model.objects.iter() {
objects.push(self.spawn_object(object, transform));
}
objects
}
pub fn spawn_object(
&mut self,
object: &model::LoadedObject,
root_transform: &glam::Mat4,
) -> ObjectWidget {
let transform = cyborg::scene::Transform {
transform: *root_transform * object.transform.to_mat4(),
};
let mut entities = Vec::new();
for mesh in object.meshes.iter() {
let mesh = cyborg::scene::Mesh { mesh: *mesh };
let entity = self.world.push((mesh, transform.clone()));
entities.push(entity);
}
let mut children = Vec::new();
for child in object.children.iter() {
children.push(self.spawn_object(child, root_transform));
}
ObjectWidget {
name: object.name.clone(),
transform: object.transform.clone(),
entities,
children,
dirty: false,
children_dirty: false,
}
}
}

View File

@ -1,5 +1,5 @@
use crate::model::LoadedModel;
use crate::render::RenderState;
use crate::world::World;
use mlua::{Lua, LuaSerdeExt, Result, Value};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
@ -10,7 +10,7 @@ impl mlua::UserData for LoadedModel {}
pub struct ScriptData {
root_path: PathBuf,
render_state: Arc<RwLock<RenderState>>,
world: Arc<RwLock<World>>,
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
@ -31,7 +31,7 @@ impl Transform {
fn load_gltf(lua: &Lua, filename: String) -> Result<LoadedModel> {
let data = lua.app_data_ref::<ScriptData>().unwrap();
let model = crate::import::load_gltf(data.root_path.join(filename).into());
let loaded = data.render_state.write().load_model(&model);
let loaded = data.world.write().load_model(&model);
Ok(loaded)
}
@ -39,7 +39,7 @@ fn spawn_model(lua: &Lua, model: (LoadedModel, Value)) -> Result<()> {
let data = lua.app_data_ref::<ScriptData>().unwrap();
let transform: Transform = lua.from_value(model.1)?;
let transform = transform.to_mat4();
data.render_state.write().spawn_model(&model.0, &transform);
data.world.write().spawn_model(&model.0, &transform);
Ok(())
}
@ -48,12 +48,12 @@ pub struct Script {
}
impl Script {
pub fn new(file: &Path, render_state: Arc<RwLock<RenderState>>) -> Self {
pub fn new(file: &Path, world: Arc<RwLock<World>>) -> Self {
let lua = Lua::new();
let data = ScriptData {
root_path: file.parent().unwrap().into(),
render_state,
world,
};
lua.set_app_data(data);

View File

@ -1,8 +1,11 @@
use crate::winit;
use crossbeam_channel::Sender;
use cyborg::camera::Flycam;
use legion::Entity;
use std::path::PathBuf;
use crate::winit;
use crate::world::{Object, World};
#[derive(Clone, Debug)]
pub enum FileEvent {
Save,
@ -23,6 +26,11 @@ pub enum Workspace {
NodeEditor,
}
pub struct UiResources<'a> {
pub world: &'a mut World,
pub viewport: &'a mut ViewportWidget,
}
pub struct UserInterface {
file_sender: Sender<FileEvent>,
developer_mode: bool,
@ -52,12 +60,7 @@ impl UserInterface {
self.quit
}
pub fn run(
&mut self,
ctx: &egui::Context,
viewport: &mut ViewportWidget,
objects: &mut [ObjectWidget],
) {
pub fn run(&mut self, ctx: &egui::Context, resources: &mut UiResources) {
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
egui::menu::bar(ui, |ui| self.ui_menu_bar(ui));
});
@ -98,7 +101,7 @@ impl UserInterface {
}
match self.workspace {
Workspace::Scene => self.ui_scene(ctx, viewport, objects),
Workspace::Scene => self.ui_scene(ctx, resources),
Workspace::NodeEditor => {
egui::CentralPanel::default().show(ctx, |ui| self.ui_node_editor(ui));
}
@ -194,28 +197,65 @@ impl UserInterface {
ui.selectable_value(&mut self.workspace, Workspace::NodeEditor, "Node Editor");
}
pub fn ui_scene(
&mut self,
ctx: &egui::Context,
viewport: &mut ViewportWidget,
objects: &mut [ObjectWidget],
) {
pub fn ui_scene(&mut self, ctx: &egui::Context, resources: &mut UiResources) {
egui::SidePanel::left("objects_panel")
.resizable(true)
.show(ctx, |ui| {
egui::ScrollArea::vertical().show(ui, |ui| {
for (index, object) in objects.iter_mut().enumerate() {
object.ui(index, ui);
}
});
});
.show(ctx, |ui| self.ui_objects(ui, resources));
egui::CentralPanel::default().show(ctx, |ui| {
viewport.show(ui);
resources.viewport.show(ui);
ui.heading("Viewport");
});
}
pub fn ui_objects(&mut self, ui: &mut egui::Ui, resources: &mut UiResources) {
egui::ScrollArea::vertical().show(ui, |ui| {
let roots = resources.world.root_objects.to_owned();
for object in roots.into_iter() {
self.ui_object(ui, resources, object);
}
});
}
pub fn ui_object(
&mut self,
ui: &mut egui::Ui,
resources: &mut UiResources,
id: Entity,
) -> bool {
let object: &mut Object = resources.world.get_component_mut(id);
let name = object
.name
.as_ref()
.unwrap_or(&"<unnamed>".into())
.to_owned();
let children = object.children.to_owned();
let mut any_dirty = false;
egui::CollapsingHeader::new(name)
.id_source(format!("child_{:?}", id))
.default_open(true)
.show(ui, |ui| {
let object: &mut Object = resources.world.get_component_mut(id);
object.ui(ui);
for child in children.into_iter() {
if self.ui_object(ui, resources, child) {
any_dirty = true;
}
}
});
let object: &mut Object = resources.world.get_component_mut(id);
if any_dirty {
object.children_dirty = true;
}
object.dirty || any_dirty
}
pub fn ui_node_editor(&mut self, ui: &mut egui::Ui) {
ui.label("Node editor goes here!");
}
@ -359,116 +399,3 @@ impl ViewportWidget {
}
}
}
pub struct ObjectWidget {
pub name: Option<String>,
pub transform: crate::model::Transform,
pub entities: Vec<legion::Entity>,
pub children: Vec<ObjectWidget>,
pub dirty: bool,
pub children_dirty: bool,
}
impl ObjectWidget {
pub fn ui(&mut self, index: usize, ui: &mut egui::Ui) {
egui::CollapsingHeader::new(self.name.as_ref().unwrap_or(&"<unnamed>".into()))
.id_source(format!("child_{}", index))
.default_open(true)
.show(ui, |ui| {
self.ui_self(ui);
for (index, child) in self.children.iter_mut().enumerate() {
child.ui(index, ui);
if child.dirty || child.children_dirty {
self.children_dirty = true;
}
}
});
}
pub fn ui_self(&mut self, ui: &mut egui::Ui) {
egui::Grid::new("root_object")
.num_columns(4)
.striped(true)
.show(ui, |ui| {
ui.label("Position: ");
self.ui_position(ui);
ui.end_row();
ui.label("Rotation: ");
self.ui_rotation(ui);
ui.end_row();
ui.label("Scale: ");
self.ui_scale(ui);
ui.end_row();
});
}
pub fn ui_position(&mut self, ui: &mut egui::Ui) {
let speed = 0.1 * self.transform.scale;
if Self::ui_vec3(ui, speed, &mut self.transform.position) {
self.dirty = true;
}
}
pub fn ui_rotation(&mut self, ui: &mut egui::Ui) {
let axes = &mut self.transform.rotation;
if ui.drag_angle(&mut axes.x).changed() {
self.dirty = true;
}
if ui.drag_angle(&mut axes.y).changed() {
self.dirty = true;
}
if ui.drag_angle(&mut axes.z).changed() {
self.dirty = true;
}
}
pub fn ui_vec3(ui: &mut egui::Ui, speed: f32, vec3: &mut glam::Vec3) -> bool {
let x_drag = egui::DragValue::new(&mut vec3.x).speed(speed);
let dirty = ui.add(x_drag).changed();
let dirty = dirty || {
// For some reason, Rust complains if this isn't in a block
let y_drag = egui::DragValue::new(&mut vec3.y).speed(speed);
ui.add(y_drag).changed()
};
let z_drag = egui::DragValue::new(&mut vec3.z).speed(speed);
let dirty = dirty || ui.add(z_drag).changed();
dirty
}
pub fn ui_scale(&mut self, ui: &mut egui::Ui) {
let scale_speed = self.transform.scale * 0.01;
let drag = egui::DragValue::new(&mut self.transform.scale)
.clamp_range(0.0..=f32::INFINITY)
.speed(scale_speed);
if ui.add(drag).changed() {
self.dirty = true;
}
}
pub fn flush_dirty(&mut self, mut f: impl FnMut(&mut Self)) {
let mut stack = vec![self];
while let Some(parent) = stack.pop() {
if parent.dirty {
parent.dirty = false;
f(parent);
}
if parent.children_dirty {
parent.children_dirty = false;
for child in parent.children.iter_mut() {
stack.push(child);
}
}
}
}
}

203
editor/src/world.rs Normal file
View File

@ -0,0 +1,203 @@
use legion::Entity;
use crate::model;
use std::ops::{Deref, DerefMut};
pub struct World {
pub world: legion::World,
pub resources: legion::Resources,
pub root_objects: Vec<Entity>,
}
impl Deref for World {
type Target = legion::World;
fn deref(&self) -> &legion::World {
&self.world
}
}
impl DerefMut for World {
fn deref_mut(&mut self) -> &mut legion::World {
&mut self.world
}
}
impl World {
pub fn new() -> Self {
Self {
world: Default::default(),
resources: Default::default(),
root_objects: vec![],
}
}
pub fn execute(&mut self, schedule: &mut legion::Schedule) {
schedule.execute(&mut self.world, &mut self.resources);
}
pub fn get_component_mut<T: 'static + Send + Sync>(&mut self, entity: Entity) -> &mut T {
self.entry(entity).unwrap().into_component_mut().unwrap()
}
pub fn spawn_model(&mut self, model: &model::LoadedModel, transform: &glam::Mat4) -> Entity {
let mut children = Vec::new();
for object in model.objects.iter() {
children.push(self.spawn_object(object, transform));
}
let root = self.world.push((Object {
name: model.name.to_owned(),
transform: model::Transform::default(),
entities: Default::default(),
children,
dirty: false,
children_dirty: false,
},));
self.root_objects.push(root);
root
}
pub fn spawn_object(
&mut self,
object: &model::LoadedObject,
root_transform: &glam::Mat4,
) -> Entity {
let transform = cyborg::scene::Transform {
transform: *root_transform * object.transform.to_mat4(),
};
let mut entities = Vec::new();
for mesh in object.meshes.iter() {
let mesh = cyborg::scene::Mesh { mesh: *mesh };
let entity = self.world.push((mesh, transform.clone()));
entities.push(entity);
}
let mut children = Vec::new();
for child in object.children.iter() {
children.push(self.spawn_object(child, root_transform));
}
self.world.push((Object {
name: object.name.clone(),
transform: object.transform.clone(),
entities,
children,
dirty: false,
children_dirty: false,
},))
}
pub fn flush_dirty_objects(&mut self) {
let mut stack = self.root_objects.to_owned();
while let Some(parent) = stack.pop() {
let parent: &mut Object = self
.world
.entry(parent)
.unwrap()
.into_component_mut()
.unwrap();
if parent.children_dirty {
parent.children_dirty = false;
stack.extend_from_slice(&parent.children);
}
if parent.dirty {
parent.dirty = false;
let transform = parent.transform.to_mat4();
for entity in parent.entities.to_owned().into_iter() {
self.world
.entry(entity)
.unwrap()
.get_component_mut::<cyborg::scene::Transform>()
.unwrap()
.transform = transform;
}
}
}
}
}
pub struct Object {
pub name: Option<String>,
pub transform: model::Transform,
pub entities: Vec<Entity>,
pub children: Vec<Entity>,
pub dirty: bool,
pub children_dirty: bool,
}
impl Object {
pub fn ui(&mut self, ui: &mut egui::Ui) {
egui::Grid::new("root_object")
.num_columns(4)
.striped(true)
.show(ui, |ui| {
ui.label("Position: ");
self.ui_position(ui);
ui.end_row();
ui.label("Rotation: ");
self.ui_rotation(ui);
ui.end_row();
ui.label("Scale: ");
self.ui_scale(ui);
ui.end_row();
});
}
pub fn ui_position(&mut self, ui: &mut egui::Ui) {
let speed = 0.1 * self.transform.scale;
if Self::ui_vec3(ui, speed, &mut self.transform.position) {
self.dirty = true;
}
}
pub fn ui_rotation(&mut self, ui: &mut egui::Ui) {
let axes = &mut self.transform.rotation;
if ui.drag_angle(&mut axes.x).changed() {
self.dirty = true;
}
if ui.drag_angle(&mut axes.y).changed() {
self.dirty = true;
}
if ui.drag_angle(&mut axes.z).changed() {
self.dirty = true;
}
}
pub fn ui_vec3(ui: &mut egui::Ui, speed: f32, vec3: &mut glam::Vec3) -> bool {
let x_drag = egui::DragValue::new(&mut vec3.x).speed(speed);
let dirty = ui.add(x_drag).changed();
let dirty = dirty || {
// For some reason, Rust complains if this isn't in a block
let y_drag = egui::DragValue::new(&mut vec3.y).speed(speed);
ui.add(y_drag).changed()
};
let z_drag = egui::DragValue::new(&mut vec3.z).speed(speed);
let dirty = dirty || ui.add(z_drag).changed();
dirty
}
pub fn ui_scale(&mut self, ui: &mut egui::Ui) {
let scale_speed = self.transform.scale * 0.01;
let drag = egui::DragValue::new(&mut self.transform.scale)
.clamp_range(0.0..=f32::INFINITY)
.speed(scale_speed);
if ui.add(drag).changed() {
self.dirty = true;
}
}
}