diff --git a/Cargo.lock b/Cargo.lock index 3c79383..e8c0c9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -586,7 +586,9 @@ dependencies = [ "async-std", "bytemuck", "crossbeam", + "csv", "env_logger", + "futures", "glam", "identconv", "log", @@ -600,6 +602,7 @@ dependencies = [ "paste", "rand", "rodio", + "serde", "sfsm", "truth", "wgpu", @@ -874,6 +877,27 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "cursor-icon" version = "1.1.0" @@ -1126,6 +1150,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -1133,6 +1172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1141,6 +1181,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -1160,6 +1211,47 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "gethostname" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index ac16442..091115d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,8 @@ identconv = "0.2.0" paste = "1.0.15" crossbeam = "0.8.4" async-std = { version = "1.13.0" } +futures = "0.3.31" + +[dev-dependencies] +csv = "1.3.0" +serde = { version = "1.0.214", features = ["derive"] } diff --git a/examples/inst_search.rs b/examples/inst_search.rs new file mode 100644 index 0000000..27389f7 --- /dev/null +++ b/examples/inst_search.rs @@ -0,0 +1,83 @@ +use std::{ + collections::HashMap, fs::{read_dir, read_to_string, File} +}; + +use serde::Serialize; +use truth::{context::RootEmitter, io::BinReader, AnmFile, Game, Mapfile, StdFile}; + +// #[derive(Serialize)] +// struct Count { +// name: String, +// count: u32 +// } + +fn parse_files( + map_file_name: &str, + ext: &str, + csv_path: &str, + hit_file: impl Fn(&mut BinReader) -> Vec, +) { + let map_file = Mapfile::load(map_file_name, Some(Game::Th11), &RootEmitter::new_stderr(), |path| { + Ok((None, read_to_string(map_file_name).unwrap())) + }) + .unwrap(); + let dir = read_dir("assets").unwrap(); + let mut map = HashMap::new(); + for file in dir { + let file = file.unwrap(); + if file.path().extension().unwrap_or_default() != ext { + continue; + } + + log::info!("file: {file:?}"); + for op in hit_file( + &mut BinReader::from_reader( + &RootEmitter::new_stderr(), + &file.file_name().to_string_lossy().to_owned(), + &File::open(file.path()).unwrap(), + ), + ) { + *map.entry(op).or_insert(0) += 1; + + }; + } + + let mut csv = csv::Writer::from_path(csv_path).unwrap(); + let mut vec = map.into_iter().collect::>(); + vec.sort_by_key(|c| c.0); + for (op, count) in vec { + log::info!("hitting op {op}"); + let str_name = map_file + .ins_names + .iter() + .find(|i| i.0 == op as i32) + .map(|v| v.1.value.as_str().to_owned()) + .unwrap_or(format!("unk_inst_{op}")); + csv.write_field(str_name).unwrap(); + csv.write_field(op.to_string()).unwrap(); + csv.write_field(count.to_string()).unwrap(); + csv.write_record(None::<&[u8]>).unwrap(); + } + + csv.flush().unwrap(); +} + +fn main() { + env_logger::init(); + + parse_files("utils/th11.eclm", "ecl", "target/ecl_inst.csv", |reader| { + let file = truth::StackEclFile::read_from_stream(reader, Game::Th11).unwrap(); + + file.subs.values().flat_map(|sub| sub.iter().map(|f| f.opcode)).collect() + }); + parse_files("utils/th095.stdm", "std", "target/std_inst.csv", | reader| { + let file = StdFile::read_from_stream(reader, Game::Th11).unwrap(); + + file.script.into_iter().map(|f|f.opcode).collect() + }); + parse_files("utils/v4.anmm", "anm", "target/anm_inst.csv", | reader| { + let file = AnmFile::read_from_stream(reader, Game::Th11, false).unwrap(); + + file.entries.iter().flat_map(|f|f.scripts.values()).flat_map(|f|f.instrs.iter()).map(|f|f.opcode).collect() + }); +} diff --git a/src/engine.rs b/src/engine.rs index c9474b9..c109485 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,8 +1,5 @@ -use std::{ - cell::RefCell, rc::Rc, sync::{Arc, Mutex} -}; +use std::sync::{Arc, Mutex}; -use crossbeam::atomic::AtomicCell; use wgpu::{ naga::FastHashMap, Backends, CommandEncoder, Device, DeviceDescriptor, Features, Instance, InstanceDescriptor, InstanceFlags, Queue, RequestAdapterOptions, Surface, SurfaceConfiguration, Texture, TextureFormat, TextureUsages }; @@ -60,7 +57,7 @@ impl Keys { } fn tick_keys(&mut self) { - for (key, state) in &mut self.values { + for (_, state) in &mut self.values { if state.is_down() { *state = KeyState::Held; } @@ -146,7 +143,7 @@ impl<'a> Engine<'a> { config: &config.clone(), }); - let mut engine = Self { + Self { surface, device, queue, @@ -158,9 +155,7 @@ impl<'a> Engine<'a> { focused: false, state, - }; - - engine + } } pub fn handle_event(&mut self, window_event: &WindowEvent) { @@ -236,10 +231,10 @@ impl<'a> Engine<'a> { } pub struct UpdateContext<'a> { - pub device: &'a Device, - pub queue: &'a Queue, + pub device: &'a Arc, + pub queue: &'a Arc, pub keys: &'a Keys, - pub window: &'a Window, + pub window: &'a Arc, pub config: &'a Arc>, } diff --git a/src/game.rs b/src/game.rs index 2ae8e67..98cc322 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,10 +1,11 @@ -use std::{rc::Rc, sync::Arc}; +use std::{path::Path, sync::Arc}; use anm::LoadedFile; use states::GameStateMachine; -use wgpu::RenderPass; -use crate::engine::{self, Engine, EngineState, UpdateContext}; +use crate::{ + engine::{EngineState, UpdateContext}, utils::soon::Soon +}; mod anm; mod snd; @@ -23,6 +24,15 @@ struct GameContext<'a> { } impl GameContext<'_> { + pub fn start_load_anm(&mut self, file_name: impl AsRef) -> Soon> { + self.anm_manager.start_load_anm( + self.engine.device.clone(), + self.engine.queue.clone(), + Some(self.engine.window.inner_size()), + file_name, + ) + } + pub fn load_anm(&mut self, file_name: &str) -> Arc { self.anm_manager.load_anm(&self.engine.device, &self.engine.queue, &self.engine.window, file_name) } diff --git a/src/game/anm/loaded_file.rs b/src/game/anm/loaded_file.rs index e17d81b..4c28f30 100644 --- a/src/game/anm/loaded_file.rs +++ b/src/game/anm/loaded_file.rs @@ -7,7 +7,7 @@ use truth::{context::RootEmitter, io::BinReader, AnmFile, Game}; use wgpu::{ naga::FastHashMap, util::DeviceExt, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Device, Extent3d, Queue, ShaderStages, Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages }; -use winit::window::Window; +use winit::dpi::PhysicalSize; use super::{ image::{produce_image_from_entry, Image}, vm::opcodes::{Instruction, Op} @@ -22,11 +22,19 @@ pub struct LoadedEntry { } impl LoadedEntry { - fn load(device: &Device, queue: &Queue, window: &Window, entry: &truth::anm::Entry) -> Arc { + fn load( + device: &Device, + queue: &Queue, + size: Option>, + entry: &truth::anm::Entry, + ) -> Arc { let image = entry.img_data().is_some().then(|| produce_image_from_entry(&entry).expect("failed to parse...")); let (width, height, usages) = if *entry.path == "@R" { - let size = window.inner_size(); + let Some(size) = size else { + panic!("loaded window resolution dependent anm without a size passed"); + }; + ( size.width, size.height, @@ -159,7 +167,7 @@ impl Add for SpriteUvs { pub struct LoadedScript { pub instructions: Vec, - pub interrupts: FastHashMap, + pub interrupts: FastHashMap, } impl LoadedScript { @@ -179,7 +187,7 @@ impl LoadedScript { .enumerate() .filter_map(|(index, inst)| { if let Op::InterruptLabel(label) = inst.op { - Some((label, index)) + Some((label, (index, inst.time))) } else { None } @@ -199,7 +207,13 @@ pub struct LoadedFile { } impl LoadedFile { - pub fn load(device: &Device, queue: &Queue, window: &Window, file_name: &str, file_data: &[u8]) -> Self { + pub fn load( + device: &Device, + queue: &Queue, + size: Option>, + file_name: &str, + file_data: &[u8], + ) -> Self { let file = AnmFile::read_from_stream( &mut BinReader::from_reader(&RootEmitter::new_stderr(), file_name, Cursor::new(file_data)), Game::Th11, @@ -210,7 +224,7 @@ impl LoadedFile { let scripts = file.entries.iter().flat_map(|entry| &entry.scripts).map(|(_, script)| LoadedScript::load(script)).collect(); - let entries = file.entries.iter().map(|entry| LoadedEntry::load(device, queue, window, entry)).collect::>(); + let entries = file.entries.iter().map(|entry| LoadedEntry::load(device, queue, size, entry)).collect::>(); let mut sprite_entries = NonOverlappingIntervalTree::new(); diff --git a/src/game/anm/manager.rs b/src/game/anm/manager.rs deleted file mode 100644 index 91bc105..0000000 --- a/src/game/anm/manager.rs +++ /dev/null @@ -1,407 +0,0 @@ -use std::{ - cell::RefCell, collections::VecDeque, num::NonZero, path::Path, rc::{Rc, Weak}, sync::Arc -}; - -use bytemuck::{bytes_of, Pod, Zeroable}; -use glam::{Mat4, Vec3, Vec4}; -use num_traits::FloatConst; -use wgpu::{ - include_wgsl, naga::FastHashMap, util::{BufferInitDescriptor, DeviceExt}, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BlendState, Buffer, BufferUsages, ColorTargetState, ColorWrites, DepthStencilState, Device, FragmentState, PipelineLayout, PipelineLayoutDescriptor, PrimitiveState, Queue, RenderPipeline, RenderPipelineDescriptor, ShaderModule, ShaderStages, Texture, TextureFormat, VertexState -}; -use winit::window::Window; - -use crate::engine::UpdateContext; - -use super::{ - loaded_file::{LoadedFile, SpriteUvs}, AnmVm -}; - -mod location; -mod rendering; - -pub use location::VmLocation; -pub type Vm = Rc>; -pub type WeakVm = Weak>; - -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C, packed)] -struct Uniform { - proj_matrix: Mat4, -} - -pub struct Manager { - anm_files: FastHashMap>, - world_backbuffer_anm: WeakVm, - - ui_vms: VecDeque, - world_vms: VecDeque, - - depth_texture: Texture, - ui_uniform: Uniform, - _world_uniform: Uniform, - uniform_buffer: Buffer, - render_bind_group: BindGroup, - render_pipeline_layout: PipelineLayout, - render_shader: ShaderModule, - clear_pipeline: RenderPipeline, - blit_pipeline: RenderPipeline, -} - -impl Manager { - pub fn new(context: &UpdateContext) -> Manager { - let depth_texture = Self::create_depth_texture(context.device, context.window.inner_size()); - - let texture_bind_group_layout = context.device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[BindGroupLayoutEntry { - binding: 0, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - visibility: ShaderStages::FRAGMENT, - }], - label: None, - }); - - let bind_group_layout = context.device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: None, - entries: &[ - BindGroupLayoutEntry { - binding: 0, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - visibility: ShaderStages::FRAGMENT, - }, - BindGroupLayoutEntry { - binding: 1, - count: None, - visibility: ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: Some(NonZero::new(std::mem::size_of::() as u64).unwrap()), - }, - }, - ], - }); - - let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("sampler"), - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); - - let ui_uniform = Uniform { - proj_matrix: Mat4::orthographic_lh(0.0, 640.0, 480.0, 0.0, 100.0, -100.0), - }; - - let uniform_buffer = context.device.create_buffer_init(&BufferInitDescriptor { - contents: bytes_of(&ui_uniform), - label: Some("uniform"), - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - }); - - let render_bind_group = context.device.create_bind_group(&BindGroupDescriptor { - label: Some("render bind group"), - layout: &bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::Sampler(&sampler), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Buffer(uniform_buffer.as_entire_buffer_binding()), - }, - ], - }); - - let pipeline_layout = context.device.create_pipeline_layout(&PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&texture_bind_group_layout, &bind_group_layout], - push_constant_ranges: &[], - }); - - let render_shader = context.device.create_shader_module(include_wgsl!("./render.wgsl")); - let blit_shader = context.device.create_shader_module(include_wgsl!("./blit_screen.wgsl")); - let clear_shader = context.device.create_shader_module(include_wgsl!("./clear.wgsl")); - - let config = context.config.lock().unwrap(); - - let clear_pipeline = context.device.create_render_pipeline(&RenderPipelineDescriptor { - label: Some("clear"), - layout: Some(&context.device.create_pipeline_layout(&PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[], - push_constant_ranges: &[], - })), - vertex: VertexState { - buffers: &[], - module: &clear_shader, - compilation_options: Default::default(), - entry_point: "vs_main", - }, - primitive: PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: Default::default(), - fragment: Some(FragmentState { - targets: &[Some(ColorTargetState { - format: config.format, - blend: Some(BlendState::REPLACE), - write_mask: ColorWrites::ALL, - })], - compilation_options: Default::default(), - entry_point: "fs_main", - module: &clear_shader, - }), - multiview: None, - cache: None, - }); - - let blit_pipeline = context.device.create_render_pipeline(&RenderPipelineDescriptor { - label: Some("blit"), - layout: Some(&pipeline_layout), - vertex: VertexState { - buffers: &[], - module: &blit_shader, - compilation_options: Default::default(), - entry_point: "vs_main", - }, - primitive: PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: Default::default(), - fragment: Some(FragmentState { - targets: &[Some(ColorTargetState { - format: config.format, - blend: Some(BlendState::ALPHA_BLENDING), - write_mask: ColorWrites::ALL, - })], - compilation_options: Default::default(), - entry_point: "fs_main", - module: &blit_shader, - }), - multiview: None, - cache: None, - }); - let world_uniform = Uniform { - proj_matrix: Mat4::perspective_lh( - 45.0 * (f32::PI() / 180.0), - config.width as f32 / config.height as f32, - 0.1, - 1000.0, - ), - }; - - Manager { - anm_files: FastHashMap::default(), - world_backbuffer_anm: Weak::new(), - - ui_vms: VecDeque::new(), - world_vms: VecDeque::new(), - - depth_texture, - ui_uniform, - _world_uniform: world_uniform, - uniform_buffer, - render_bind_group, - render_pipeline_layout: pipeline_layout, - render_shader, - clear_pipeline, - blit_pipeline, - } - } - - pub fn load_anm( - &mut self, - device: &Device, - queue: &Queue, - window: &Window, - file_name: impl AsRef, - ) -> Arc { - let file_name_str = file_name.as_ref().to_str().unwrap().to_owned(); - if let Some(loaded_anm) = self.anm_files.get(&file_name_str) { - return loaded_anm.clone(); - } - - let file_data = std::fs::read(Path::new("./assets").join(file_name)).expect("failed to load anm file"); - let loaded_anm = Arc::new(LoadedFile::load(device, queue, window, &file_name_str, &file_data)); - - self.anm_files.insert(file_name_str.to_owned(), loaded_anm.clone()); - loaded_anm - } - - #[allow(unused)] - pub fn set_world_backbuffer_anm(&mut self, file: Arc, script: usize) -> Vm { - let vm = Rc::new(RefCell::new(AnmVm::new(file, None, false, false, script, 0))); - self.update_single(&vm); - - self.world_backbuffer_anm = Rc::downgrade(&vm); - - vm - } - - pub fn new_vm( - &mut self, - file: Arc, - origin: Option, - ticked_by_parent: bool, - debug: bool, - script: usize, - location: VmLocation, - ) -> Vm { - let layer = location.layer(); - - let vm = Rc::new(RefCell::new(AnmVm::new( - file, - origin, - ticked_by_parent, - debug, - script, - layer, - ))); - self.update_single(&vm); - - let mut context = ManagerUpdate::new(); - vm.borrow_mut().tick(&mut context); - - let (vm_list, front) = match location { - VmLocation::Ui { front, .. } => (&mut self.ui_vms, front), - VmLocation::World { front, .. } => (&mut self.world_vms, front), - }; - - if front { - vm_list.push_front(Rc::downgrade(&vm)) - } else { - vm_list.push_back(Rc::downgrade(&vm)); - } - - vm - } - - fn update_single(&mut self, vm: &Vm) { - let mut context = ManagerUpdate::new(); - vm.borrow_mut().tick(&mut context); - context.apply_lists(&mut self.ui_vms, &mut self.world_vms); - } - - fn update_list(list: &mut VecDeque, context: &mut ManagerUpdate) { - list.retain(|value| { - if let Some(rc) = value.upgrade() { - !rc.borrow().deleted - } else { - false - } - }); - - for vm in list { - if let Some(vm) = vm.upgrade() { - if !vm.borrow().ticked_by_parent { - vm.borrow_mut().tick(context); - } - } - } - } - - pub fn update(&mut self) { - let mut context = ManagerUpdate::new(); - - Self::update_list(&mut self.world_vms, &mut context); - Self::update_list(&mut self.ui_vms, &mut context); - - context.apply_lists(&mut self.ui_vms, &mut self.world_vms); - } -} - -#[derive(Default)] -pub(super) struct ManagerUpdate { - new_ui_vms_front: Vec, - new_ui_vms_back: Vec, - new_world_vms_front: Vec, - new_world_vms_back: Vec, -} - -impl ManagerUpdate { - fn new() -> Self { - Self::default() - } - - pub fn new_vm( - &mut self, - file: Arc, - origin: Option, - ticked_by_parent: bool, - debug: bool, - script: usize, - location: VmLocation, - ) -> Vm { - let layer = location.layer(); - - let vm = Rc::new(RefCell::new(AnmVm::new( - file, - origin, - ticked_by_parent, - debug, - script, - layer, - ))); - vm.borrow_mut().tick(self); - - let vm_list = match location { - VmLocation::Ui { front, .. } => { - if front { - &mut self.new_ui_vms_front - } else { - &mut self.new_ui_vms_back - } - } - VmLocation::World { front, .. } => { - if front { - &mut self.new_world_vms_front - } else { - &mut self.new_world_vms_back - } - } - }; - - vm_list.push(Rc::downgrade(&vm)); - - vm - } - - fn apply_to_list(list: &mut VecDeque, front: Vec, back: Vec) { - for vm in front.into_iter().rev() { - list.push_front(vm.clone()); - } - list.extend(back.into_iter()); - } - - fn apply_lists(self, ui_list: &mut VecDeque, world_list: &mut VecDeque) { - Self::apply_to_list(ui_list, self.new_ui_vms_front, self.new_ui_vms_back); - Self::apply_to_list(world_list, self.new_world_vms_front, self.new_world_vms_back); - } -} diff --git a/src/game/anm/manager/loading.rs b/src/game/anm/manager/loading.rs new file mode 100644 index 0000000..69ac7e1 --- /dev/null +++ b/src/game/anm/manager/loading.rs @@ -0,0 +1,57 @@ +use std::{path::Path, sync::Arc}; + +use async_std::fs; +use wgpu::{Device, Queue}; +use winit::{dpi::PhysicalSize, window::Window}; + +use crate::{game::anm::LoadedFile, utils::soon::Soon}; + +use super::Manager; + +impl Manager { + pub fn start_load_anm( + &mut self, + device: Arc, + queue: Arc, + size: Option>, + file_name: impl AsRef, + ) -> Soon> { + let file_name_str = file_name.as_ref().to_str().unwrap().to_owned(); + if let Some(loaded_anm) = self.anm_files.get(&file_name_str) { + return Soon::Value(loaded_anm.clone()); + } + + let file_name = file_name.as_ref().to_owned(); + Soon::new(async move { + let file_data = fs::read(Path::new("./assets").join(file_name)).await.expect("failed to load anm file"); + let file = Arc::new(LoadedFile::load(&device, &queue, size, &file_name_str, &file_data)); + + file + }) + } + + pub fn load_anm( + &mut self, + device: &Device, + queue: &Queue, + window: &Window, + file_name: impl AsRef, + ) -> Arc { + let file_name_str = file_name.as_ref().to_str().unwrap().to_owned(); + if let Some(loaded_anm) = self.anm_files.get(&file_name_str) { + return loaded_anm.clone(); + } + + let file_data = std::fs::read(Path::new("./assets").join(file_name)).expect("failed to load anm file"); + let loaded_anm = Arc::new(LoadedFile::load( + device, + queue, + Some(window.inner_size()), + &file_name_str, + &file_data, + )); + + self.anm_files.insert(file_name_str.to_owned(), loaded_anm.clone()); + loaded_anm + } +} diff --git a/src/game/anm/manager/location.rs b/src/game/anm/manager/location.rs index 3b51ad0..96082f8 100644 --- a/src/game/anm/manager/location.rs +++ b/src/game/anm/manager/location.rs @@ -1,41 +1,74 @@ -pub enum VmLocation { - Ui { front: bool, layer: u32 }, - World { front: bool, layer: u32 }, +pub struct VmLocation { + pub world: bool, + pub front: bool, + pub child: bool, + pub layer: u32, } +#[allow(unused)] impl VmLocation { pub fn new() -> Self { - Self::World { front: false, layer: 0 } + Self { + world: true, + front: false, + child: false, + layer: 0, + } } pub fn new_ui() -> Self { - Self::Ui { + Self { + world: false, front: false, + child: false, layer: 30, } } pub fn new_child(layer: u32) -> Self { - Self::World { front: false, layer } + Self { + world: true, + front: false, + child: true, + layer, + } } pub fn new_child_ui(layer: u32) -> Self { - Self::Ui { front: false, layer } + Self { + world: false, + front: false, + child: true, + layer, + } } pub fn new_front() -> Self { - Self::World { front: true, layer: 0 } + Self { + world: true, + front: true, + child: false, + layer: 0, + } } pub fn new_ui_front() -> Self { - Self::Ui { front: true, layer: 30 } + Self { + world: false, + front: true, + child: false, + layer: 30, + } } pub fn new_child_front(layer: u32) -> Self { - Self::World { front: true, layer } + Self { + world: true, + front: true, + child: true, + layer, + } } pub fn new_child_ui_front(layer: u32) -> Self { - Self::Ui { front: true, layer } - } - - pub fn layer(&self) -> u32 { - match self { - VmLocation::Ui { layer, .. } => *layer, - VmLocation::World { layer, .. } => *layer, + Self { + world: false, + front: true, + child: true, + layer, } } } diff --git a/src/game/anm/manager/mod.rs b/src/game/anm/manager/mod.rs new file mode 100644 index 0000000..4df1a4e --- /dev/null +++ b/src/game/anm/manager/mod.rs @@ -0,0 +1,170 @@ +use std::{ + cell::RefCell, collections::VecDeque, rc::{Rc, Weak}, sync::Arc +}; + +use bytemuck::{Pod, Zeroable}; +use glam::Mat4; +use wgpu::{naga::FastHashMap, BindGroup, Buffer, PipelineLayout, RenderPipeline, ShaderModule, Texture}; + +use super::{loaded_file::LoadedFile, AnmVm}; + +mod loading; +mod location; +mod rendering; + +pub use location::VmLocation; +pub type Vm = Rc>; +pub type WeakVm = Weak>; + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +struct Uniform { + proj_matrix: Mat4, +} + +pub struct Manager { + anm_files: FastHashMap>, + world_backbuffer_anm: WeakVm, + + ui_vms: VecDeque, + world_vms: VecDeque, + + depth_texture: Texture, + ui_uniform: Uniform, + _world_uniform: Uniform, + uniform_buffer: Buffer, + render_bind_group: BindGroup, + render_pipeline_layout: PipelineLayout, + render_shader: ShaderModule, + clear_pipeline: RenderPipeline, + blit_pipeline: RenderPipeline, +} + +impl Manager { + #[allow(unused)] + pub fn set_world_backbuffer_anm(&mut self, file: Arc, script: usize) -> Vm { + let vm = Rc::new(RefCell::new(AnmVm::new(file, script, 0))); + + self.update_single(&vm); + + self.world_backbuffer_anm = Rc::downgrade(&vm); + + vm + } + + pub fn new_vm(&mut self, file: Arc, interrupt: Option, script: usize, location: VmLocation) -> Vm { + let vm = Rc::new(RefCell::new(AnmVm::new(file, script, location.layer))); + + if let Some(interrupt) = interrupt { + vm.borrow_mut().interrupt(interrupt); + } + + self.update_single(&vm); + + let mut context = ManagerUpdate::new(); + vm.borrow_mut().tick(&mut context); + + let (vm_list, front) = match location { + VmLocation { + world: false, front, .. + } => (&mut self.ui_vms, front), + VmLocation { world: true, front, .. } => (&mut self.world_vms, front), + }; + + if front { + vm_list.push_front(Rc::downgrade(&vm)) + } else { + vm_list.push_back(Rc::downgrade(&vm)); + } + + vm + } + + fn update_single(&mut self, vm: &Vm) { + let mut context = ManagerUpdate::new(); + vm.borrow_mut().tick(&mut context); + context.apply_lists(&mut self.ui_vms, &mut self.world_vms); + } + + fn update_list(list: &mut VecDeque, context: &mut ManagerUpdate) { + list.retain(|value| { + if let Some(rc) = value.upgrade() { + !rc.borrow().deleted() + } else { + false + } + }); + + for vm in list { + if let Some(vm) = vm.upgrade() { + if !vm.borrow().ticked_by_parent { + vm.borrow_mut().tick(context); + } + } + } + } + + pub fn update(&mut self) { + let mut context = ManagerUpdate::new(); + + Self::update_list(&mut self.world_vms, &mut context); + Self::update_list(&mut self.ui_vms, &mut context); + + context.apply_lists(&mut self.ui_vms, &mut self.world_vms); + } +} + +#[derive(Default)] +pub(super) struct ManagerUpdate { + new_ui_vms_front: Vec, + new_ui_vms_back: Vec, + new_world_vms_front: Vec, + new_world_vms_back: Vec, +} + +impl ManagerUpdate { + fn new() -> Self { + Self::default() + } + + pub fn new_vm(&mut self, file: Arc, script: usize, location: VmLocation) -> Vm { + let vm = Rc::new(RefCell::new(AnmVm::new(file, script, location.layer))); + vm.borrow_mut().ticked_by_parent = location.child; + vm.borrow_mut().tick(self); + + let vm_list = match location { + VmLocation { + world: false, front, .. + } => { + if front { + &mut self.new_ui_vms_front + } else { + &mut self.new_ui_vms_back + } + } + VmLocation { world: true, front, .. } => { + if front { + &mut self.new_world_vms_front + } else { + &mut self.new_world_vms_back + } + } + }; + + vm_list.push(Rc::downgrade(&vm)); + + vm + } + + fn apply_to_list(list: &mut VecDeque, front: Vec, back: Vec) { + for vm in front.into_iter().rev() { + list.push_front(vm.clone()); + } + list.extend(back.into_iter()); + } + + fn apply_lists(self, ui_list: &mut VecDeque, world_list: &mut VecDeque) { + Self::apply_to_list(ui_list, self.new_ui_vms_front, self.new_ui_vms_back); + Self::apply_to_list(world_list, self.new_world_vms_front, self.new_world_vms_back); + } +} diff --git a/src/game/anm/manager/rendering.rs b/src/game/anm/manager/rendering.rs index ea0924c..98e683d 100644 --- a/src/game/anm/manager/rendering.rs +++ b/src/game/anm/manager/rendering.rs @@ -1,17 +1,18 @@ -use std::collections::VecDeque; +use std::{collections::VecDeque, num::NonZero, rc::Weak}; use bytemuck::{bytes_of, Pod, Zeroable}; use glam::{Mat4, Vec2, Vec3, Vec4}; +use num_traits::FloatConst; use wgpu::{ - BufferDescriptor, BufferUsages, Color, ColorTargetState, ColorWrites, DepthStencilState, Device, Extent3d, FragmentState, LoadOp, Operations, PrimitiveState, RenderPass, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, StoreOp, TextureDescriptor, TextureFormat, TextureUsages, VertexAttribute, VertexBufferLayout, VertexState + include_wgsl, naga::FastHashMap, util::{BufferInitDescriptor, DeviceExt}, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BlendState, BufferDescriptor, BufferUsages, Color, ColorTargetState, ColorWrites, DepthStencilState, Device, Extent3d, FragmentState, LoadOp, Operations, PipelineLayoutDescriptor, PrimitiveState, RenderPass, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, ShaderStages, StoreOp, TextureDescriptor, TextureFormat, TextureUsages, VertexAttribute, VertexBufferLayout, VertexState }; use winit::dpi::PhysicalSize; use crate::{ - engine::Engine, game::anm::{loaded_file::SpriteUvs, vm::RenderingState, AnmVm} + engine::{Engine, UpdateContext}, game::anm::{loaded_file::SpriteUvs, vm::RenderingState, AnmVm} }; -use super::{Manager, WeakVm}; +use super::{Manager, Uniform, WeakVm}; #[derive(Default, Clone, Copy, Pod, Zeroable)] #[repr(C, packed)] @@ -23,6 +24,194 @@ pub(super) struct Instance { } impl Manager { + pub fn new(context: &UpdateContext) -> Manager { + let depth_texture = Self::create_depth_texture(context.device, context.window.inner_size()); + + let texture_bind_group_layout = context.device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + visibility: ShaderStages::FRAGMENT, + }], + label: None, + }); + + let bind_group_layout = context.device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[ + BindGroupLayoutEntry { + binding: 0, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + visibility: ShaderStages::FRAGMENT, + }, + BindGroupLayoutEntry { + binding: 1, + count: None, + visibility: ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(NonZero::new(std::mem::size_of::() as u64).unwrap()), + }, + }, + ], + }); + + let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("sampler"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let ui_uniform = Uniform { + proj_matrix: Mat4::orthographic_lh(0.0, 640.0, 480.0, 0.0, 100.0, -100.0), + }; + + let uniform_buffer = context.device.create_buffer_init(&BufferInitDescriptor { + contents: bytes_of(&ui_uniform), + label: Some("uniform"), + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, + }); + + let render_bind_group = context.device.create_bind_group(&BindGroupDescriptor { + label: Some("render bind group"), + layout: &bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::Sampler(&sampler), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Buffer(uniform_buffer.as_entire_buffer_binding()), + }, + ], + }); + + let pipeline_layout = context.device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&texture_bind_group_layout, &bind_group_layout], + push_constant_ranges: &[], + }); + + let render_shader = context.device.create_shader_module(include_wgsl!("../render.wgsl")); + let blit_shader = context.device.create_shader_module(include_wgsl!("../blit_screen.wgsl")); + let clear_shader = context.device.create_shader_module(include_wgsl!("../clear.wgsl")); + + let config = context.config.lock().unwrap(); + + let clear_pipeline = context.device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some("clear"), + layout: Some(&context.device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + })), + vertex: VertexState { + buffers: &[], + module: &clear_shader, + compilation_options: Default::default(), + entry_point: "vs_main", + }, + primitive: PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: Some(DepthStencilState { + format: TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: Default::default(), + fragment: Some(FragmentState { + targets: &[Some(ColorTargetState { + format: config.format, + blend: Some(BlendState::REPLACE), + write_mask: ColorWrites::ALL, + })], + compilation_options: Default::default(), + entry_point: "fs_main", + module: &clear_shader, + }), + multiview: None, + cache: None, + }); + + let blit_pipeline = context.device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some("blit"), + layout: Some(&pipeline_layout), + vertex: VertexState { + buffers: &[], + module: &blit_shader, + compilation_options: Default::default(), + entry_point: "vs_main", + }, + primitive: PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: Some(DepthStencilState { + format: TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: Default::default(), + fragment: Some(FragmentState { + targets: &[Some(ColorTargetState { + format: config.format, + blend: Some(BlendState::ALPHA_BLENDING), + write_mask: ColorWrites::ALL, + })], + compilation_options: Default::default(), + entry_point: "fs_main", + module: &blit_shader, + }), + multiview: None, + cache: None, + }); + let world_uniform = Uniform { + proj_matrix: Mat4::perspective_lh( + 45.0 * (f32::PI() / 180.0), + config.width as f32 / config.height as f32, + 0.1, + 1000.0, + ), + }; + + Manager { + anm_files: FastHashMap::default(), + world_backbuffer_anm: Weak::new(), + + ui_vms: VecDeque::new(), + world_vms: VecDeque::new(), + + depth_texture, + ui_uniform, + _world_uniform: world_uniform, + uniform_buffer, + render_bind_group, + render_pipeline_layout: pipeline_layout, + render_shader, + clear_pipeline, + blit_pipeline, + } + } + fn new_pipeline(&self, engine: &Engine, vm: &AnmVm) -> RenderPipeline { let render_pipeline = engine.device.create_render_pipeline(&RenderPipelineDescriptor { label: Some("anm"), @@ -147,12 +336,9 @@ impl Manager { rendering_state.pipeline = Some(self.new_pipeline(engine, vm)) } - // let mut matrix = Mat4::from_translation(layer_origin + vm.position()); - let offset = sprite.size.extend(1.0) * vm.anchor_offset.extend(0.0); let mut matrix = Mat4::IDENTITY; matrix *= Mat4::from_translation(layer_origin + vm.position()); matrix *= Mat4::from_rotation_translation(vm.rotation(), Vec3::ZERO); - // matrix *= Mat4::from_translation(-offset); matrix *= Mat4::from_scale((sprite.size * vm.scale()).extend(1.0)); engine.queue.write_buffer( diff --git a/src/game/anm/vm/execute.rs b/src/game/anm/vm/execute.rs index fd99e9f..9b978c0 100644 --- a/src/game/anm/vm/execute.rs +++ b/src/game/anm/vm/execute.rs @@ -12,7 +12,7 @@ use super::{ impl AnmVm { fn next_instruction(&mut self) -> Option { - if self.waiting_countdown > 0 { + if self.time.waiting() { return None; } @@ -21,7 +21,7 @@ impl AnmVm { return None; } let instruction = instructions[self.pc]; - if self.time < instruction.time { + if self.time.time() < instruction.time { return None; } @@ -30,11 +30,7 @@ impl AnmVm { } pub(super) fn advance_time(&mut self) { - if self.waiting_countdown == 0 { - self.time += 1; - } - - self.waiting_countdown = self.waiting_countdown.saturating_sub(1); + self.time.tick() } fn store_int(&mut self, var: i32, value: i32) { @@ -107,10 +103,35 @@ impl AnmVm { } } - pub(super) fn execute(&mut self, manager: &mut ManagerUpdate) { - if let Some(interrupt) = self.pending_interrupt { - + pub fn interrupt(&mut self, interrupt_id: u32) { + self.pending_interrupt = Some(interrupt_id); + + for ele in self.children.iter() { + ele.borrow_mut().interrupt(interrupt_id); } + } + + fn handle_interrupt(&mut self) { + if let Some((int_pc, int_time)) = + self.pending_interrupt.take().and_then(|int| self.script.interrupts.get(&int).cloned()) + { + self.interrupt_return = Some((self.pc, self.time)); + self.pc = int_pc; + self.time = Default::default(); + self.time.set_time(int_time) + } + } + + fn return_from_interrupt(&mut self) { + if let Some((pc, time)) = self.interrupt_return.take() { + self.pc = pc; + self.time.set_time(time.time()); + } + } + + pub(super) fn execute(&mut self, manager: &mut ManagerUpdate) { + self.handle_interrupt(); + while let Some(inst) = self.next_instruction() { if self.debug { log::info!("executing {inst:?}"); @@ -124,7 +145,7 @@ impl AnmVm { Op::Sprite(sprite) => self.sprite = sprite, Op::Jump { index, time } => { self.pc = index; - self.time = time; + self.time.set_time(time); } Op::IntOp { op, dest, input } => { let op: fn(i32, i32) -> i32 = match op { @@ -153,10 +174,7 @@ impl AnmVm { if self.debug { log::info!( "{dest} {op:?}Assign {input:?} = {}", - op_func( - self.param_float(Param::Variable(dest)), - self.param_float(input) - ) + op_func(self.param_float(Param::Variable(dest)), self.param_float(input)) ); } @@ -275,7 +293,9 @@ impl AnmVm { None, None, ), - Op::Stop => self.waiting_countdown = u32::MAX, + Op::Stop => { + self.time.stop(); + } Op::InterruptLabel(_) => {} Op::Anchor { horizontal, vertical } => { let axis = |value: i16| match value { @@ -293,36 +313,21 @@ impl AnmVm { self.sprite_mode = mode; } Op::Layer { layer } => self.layer = layer as u32, - Op::Wait { time } => self.waiting_countdown = time as u32, - Op::CaseReturn => {} + Op::Wait { time } => self.time.wait(time as u32), + Op::CaseReturn => { + self.return_from_interrupt(); + } Op::UnknownBitflag { .. } => {} Op::RandMode { mode } => self.random_mode = mode, Op::ScriptNew { script } => { - self.children.push(manager.new_vm( - self.file.clone(), - Some(self.position()), - true, - false, - script as usize, - VmLocation::new_child(self.layer), - )); + self.children.push(manager.new_vm(self.file.clone(), script as usize, VmLocation::new_child(self.layer))); } Op::ScriptNewUI { script } => { - self.children.push(manager.new_vm( - self.file.clone(), - Some(self.position()), - true, - false, - script as usize, - VmLocation::new_child_ui(self.layer), - )); + self.children.push(manager.new_vm(self.file.clone(), script as usize, VmLocation::new_child_ui(self.layer))); } Op::ScriptNewFront { script } => { self.children.push(manager.new_vm( self.file.clone(), - Some(self.position()), - true, - false, script as usize, VmLocation::new_child_front(self.layer), )); @@ -330,9 +335,6 @@ impl AnmVm { Op::ScriptNewUIFront { script } => { self.children.push(manager.new_vm( self.file.clone(), - Some(self.position()), - true, - false, script as usize, VmLocation::new_child_ui_front(self.layer), )); diff --git a/src/game/anm/vm/mod.rs b/src/game/anm/vm/mod.rs index 5571ac8..61d35c8 100644 --- a/src/game/anm/vm/mod.rs +++ b/src/game/anm/vm/mod.rs @@ -1,8 +1,8 @@ -use std::{cell::RefCell, rc::Rc, sync::Arc}; +use std::{cell::RefCell, sync::Arc}; use glam::{Quat, Vec2, Vec3}; -use log::warn; -use opcodes::{Instruction, SpriteType}; +use opcodes::SpriteType; +use timer::Timer; use wgpu::{BlendComponent, BlendFactor, BlendOperation, BlendState, Buffer, RenderPipeline}; use crate::interp::{FloatInterpolator, Vec2Interpolator, Vec3Interpolator}; @@ -13,6 +13,7 @@ use super::{ pub mod execute; pub(super) mod opcodes; +pub mod timer; pub(super) struct RenderingState { pub instance_buffer: Buffer, @@ -22,22 +23,21 @@ pub(super) struct RenderingState { pub struct AnmVm { debug: bool, - pub(super) deleted: bool, + deleted: bool, fully_static: bool, frozen: bool, pub(super) ticked_by_parent: bool, - time: i32, + time: Timer, pc: usize, pub(super) file: Arc, script: Arc, int_vars: [i32; 6], float_vars: [f32; 4], - waiting_countdown: u32, - pending_interrupt: Option, - interrupt_return: Option<(usize, i32)>, + pending_interrupt: Option, + interrupt_return: Option<(usize, Timer)>, - origin: Option, + pub origin: Vec3, position_interpolator: Vec3Interpolator, rotation_interpolator: Vec3Interpolator, scale_interpolator: Vec2Interpolator, @@ -59,32 +59,24 @@ pub struct AnmVm { } impl AnmVm { - pub(super) fn new( - file: Arc, - origin: Option, - ticked_by_parent: bool, - debug: bool, - script: usize, - default_layer: u32, - ) -> AnmVm { + pub(super) fn new(file: Arc, script: usize, default_layer: u32) -> AnmVm { let mut new = AnmVm { deleted: false, fully_static: false, frozen: false, - ticked_by_parent, - debug, + ticked_by_parent: false, + debug: false, - time: 0, + time: Timer::default(), pc: 0, script: file.scripts[script].clone(), file, int_vars: [0; 6], float_vars: [0.0; 4], - waiting_countdown: 0, pending_interrupt: None, interrupt_return: None, - origin, + origin: Vec3::ZERO, position_interpolator: Vec3Interpolator::new(Vec3::ZERO), rotation_interpolator: Vec3Interpolator::new(Vec3::ZERO), scale_interpolator: Vec2Interpolator::new(Vec2::ONE), @@ -122,10 +114,16 @@ impl AnmVm { }) } + #[allow(unused)] pub fn debug_freeze(&mut self) { self.frozen = true; } + #[allow(unused)] + pub fn debug(&mut self) { + self.debug = true; + } + pub fn scale(&self) -> Vec2 { self.scale_interpolator.current() } @@ -136,8 +134,10 @@ impl AnmVm { } pub fn position(&self) -> Vec3 { - self.origin.unwrap_or(Vec3::ZERO) + self.position_interpolator.current() + self.origin + self.position_interpolator.current() } + + #[allow(unused)] pub fn raw_position(&self) -> Vec3 { self.position_interpolator.current() } @@ -190,7 +190,7 @@ impl AnmVm { for child in &self.children { let mut child = child.borrow_mut(); - child.origin = Some(self.position()); + child.origin = self.position(); child.tick(manager); } self.children.retain(|vm| !vm.borrow().deleted); @@ -211,7 +211,7 @@ impl AnmVm { // additive blending color: BlendComponent { src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::One, + dst_factor: BlendFactor::OneMinusSrcAlpha, operation: BlendOperation::Add, }, alpha: BlendComponent::REPLACE, diff --git a/src/game/anm/vm/opcodes.rs b/src/game/anm/vm/opcodes.rs index 782eaad..c57de1c 100644 --- a/src/game/anm/vm/opcodes.rs +++ b/src/game/anm/vm/opcodes.rs @@ -292,7 +292,7 @@ pub enum Op { FlipX, FlipY, Stop, - InterruptLabel(i32), + InterruptLabel(u32), Anchor { horizontal: i16, vertical: i16, @@ -673,7 +673,7 @@ impl Instruction { Opcode::InterruptLabel => { let label = decode_args!(args, "S"); - Op::InterruptLabel(label) + Op::InterruptLabel(label as u32) } Opcode::Anchor => { let (horizontal, vertical) = decode_args!(args, "ss"); diff --git a/src/game/anm/vm/timer.rs b/src/game/anm/vm/timer.rs new file mode 100644 index 0000000..4223d70 --- /dev/null +++ b/src/game/anm/vm/timer.rs @@ -0,0 +1,41 @@ +#[derive(Default, Clone, Copy)] +pub struct Timer { + time: i32, + wait_countdown: u32, + stopped: bool, +} + +impl Timer { + pub fn time(&self) -> i32 { + self.time + } + + pub fn set_time(&mut self, time: i32) { + self.time = time; + } + + pub fn waiting(&self) -> bool { + self.wait_countdown > 0 || self.stopped + } + + pub fn wait(&mut self, time: u32) { + self.wait_countdown = time; + } + + pub fn stop(&mut self) { + self.stopped = true; + } + + pub fn tick(&mut self) { + if self.stopped { + return; + } + + if self.wait_countdown > 0 { + self.wait_countdown = self.wait_countdown.saturating_sub(1); + return; + } + + self.time += 1; + } +} diff --git a/src/game/snd/bgm/format.rs b/src/game/snd/bgm/format.rs index c53be83..7e71476 100644 --- a/src/game/snd/bgm/format.rs +++ b/src/game/snd/bgm/format.rs @@ -1,6 +1,5 @@ -use std::ffi::CStr; +use std::{ffi::CStr, ops::Range}; -use rodio::cpal::{BufferSize, SampleFormat, SampleRate, StreamConfig}; use wgpu::naga::FastHashMap; use zerocopy::{FromBytes, Immutable, KnownLayout}; @@ -8,10 +7,8 @@ pub struct BgmFormat { pub tracks: FastHashMap, } +#[derive(Clone)] pub struct BgmTrack { - pub(super) config: StreamConfig, - pub(super) format: SampleFormat, - pub(super) track_offset: u32, pub(super) intro_size: u32, pub(super) track_size: u32, @@ -36,6 +33,7 @@ impl BgmFormat { extra_byte_count: u16, pad: u16, } + type TrackName = [u8; 16]; const TRACK_NAME_SIZE: usize = size_of::(); @@ -54,16 +52,11 @@ impl BgmFormat { } let track_name = track_name.to_str().unwrap().to_owned(); + println!("track: {:?}", track_name); let track = Track::ref_from_bytes(&bytes[(start + TRACK_NAME_SIZE)..(start + TRACK_SIZE)]).unwrap(); tracks.insert( track_name, BgmTrack { - config: StreamConfig { - channels: track.channels, - sample_rate: SampleRate(track.samples_per_sec), - buffer_size: BufferSize::Fixed(track.intro_size + track.track_size), - }, - format: SampleFormat::I16, track_offset: track.track_offset, intro_size: track.intro_size, track_size: track.track_size, @@ -75,4 +68,14 @@ impl BgmFormat { Self { tracks } } + + pub fn get_track_buffers(&mut self, file_name: &str) -> (Range, Range) { + let track = self.tracks.get(file_name).expect("failed to find bgm file"); + + let intro_start = track.track_offset as usize; + let track_start = intro_start + track.intro_size as usize; + let track_end = intro_start + track.track_size as usize; + + (intro_start..track_start, track_start..track_end) + } } diff --git a/src/game/snd/bgm/mod.rs b/src/game/snd/bgm/mod.rs index db7b59d..503c916 100644 --- a/src/game/snd/bgm/mod.rs +++ b/src/game/snd/bgm/mod.rs @@ -1 +1,44 @@ +use async_std::fs::read; +use format::BgmFormat; +use rodio::{static_buffer::StaticSamplesBuffer, Sink, Source}; + pub mod format; + +pub struct BgmManager { + bgm_format: BgmFormat, + bgm_file: &'static [u8], + music_sink: Sink, +} + +impl BgmManager { + pub(super) async fn new(music_sink: Sink) -> BgmManager { + let bgm_format = BgmFormat::new(read("assets/thbgm.fmt").await.unwrap()); + let bgm_file = read("./thbgm.dat").await.unwrap(); + music_sink.set_volume(0.1); + + Self { + bgm_format, + bgm_file: bgm_file.leak(), + music_sink, + } + } + + pub fn play_track(&mut self, file_name: &str) { + let (intro_range, track_range) = self.bgm_format.get_track_buffers(file_name); + self.music_sink.clear(); + + log::info!("intro: {intro_range:?}, {track_range:?}"); + + self.music_sink.append(StaticSamplesBuffer::::new( + 2, + 44100, + bytemuck::cast_slice(&self.bgm_file[intro_range]), + )); + + self.music_sink.append( + StaticSamplesBuffer::::new(2, 44100, bytemuck::cast_slice(&self.bgm_file[track_range])).repeat_infinite(), + ); + + self.music_sink.play(); + } +} diff --git a/src/game/snd/mod.rs b/src/game/snd/mod.rs index a24b41f..df502eb 100644 --- a/src/game/snd/mod.rs +++ b/src/game/snd/mod.rs @@ -1,23 +1,42 @@ -use std::{ - fs::{self, File}, sync::Arc +use std::sync::Arc; + +use bgm::BgmManager; +use rodio::{ + dynamic_mixer::{mixer, DynamicMixerController}, OutputStream, Sink }; -use bgm::format::BgmFormat; -use memmap2::{Mmap, MmapOptions}; +use crate::utils::soon::Soon; pub mod bgm; pub struct Manager { - bgm_format: bgm::format::BgmFormat, - bgm_file: Arc, + bgm_manager: Soon, + _output_stream: OutputStream, + _sound_sink: Sink, + _sound_controller: Arc>, } impl Manager { pub fn new() -> Self { - let bgm_format = BgmFormat::new(fs::read("assets/thbgm.fmt").unwrap()); - let bgm_file = Arc::new(unsafe { - MmapOptions::new().map(&File::open("./thbgm.dat").unwrap()).expect("failed to open thbgm.dat") - }); - Self { bgm_format, bgm_file } + let (output_stream, stream_handle) = OutputStream::try_default().unwrap(); + let sound_sink = Sink::try_new(&stream_handle).unwrap(); + let music_sink = Sink::try_new(&stream_handle).unwrap(); + let (sound_controller, mixer) = mixer::(2, 44100); + sound_sink.append(mixer); + + Self { + bgm_manager: Soon::new(BgmManager::new(music_sink)), + _output_stream: output_stream, + _sound_sink: sound_sink, + _sound_controller: sound_controller, + } + } + + pub fn is_bgm_manager_loaded(&mut self) -> bool { + self.bgm_manager.is_done() + } + + pub fn get_bgm_manager(&mut self) -> &mut BgmManager { + self.bgm_manager.try_borrow_mut().unwrap() } } diff --git a/src/game/states/game/enemy/loaded_file.rs b/src/game/states/game/enemy/loaded_file.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/game/states/game/enemy/mod.rs b/src/game/states/game/enemy/mod.rs new file mode 100644 index 0000000..0876df3 --- /dev/null +++ b/src/game/states/game/enemy/mod.rs @@ -0,0 +1,2 @@ +pub mod loaded_file; +pub mod vm; diff --git a/src/game/states/game/enemy/vm/mod.rs b/src/game/states/game/enemy/vm/mod.rs new file mode 100644 index 0000000..c7c51e2 --- /dev/null +++ b/src/game/states/game/enemy/vm/mod.rs @@ -0,0 +1,5 @@ +mod opcodes; + +struct StackEntry {} + +pub struct EclVm {} diff --git a/src/game/states/game/enemy/vm/opcodes.rs b/src/game/states/game/enemy/vm/opcodes.rs new file mode 100644 index 0000000..097b08d --- /dev/null +++ b/src/game/states/game/enemy/vm/opcodes.rs @@ -0,0 +1,143 @@ +use num_derive::FromPrimitive; + +#[derive(FromPrimitive, Clone, Copy)] +enum Opcode { + Nop = 0, + + // control flow + Delete = 1, + Ret = 10, + Call = 11, + Jump = 12, + JumpEq = 13, + JumpNeq = 14, + CallAsync = 15, + CallAsyncById = 16, + KillAsync = 17, + KillAllAsync = 21, + + // stack math + StackAlloc = 40, + PushInt = 42, + SetInt = 43, + PushFloat = 44, + SetFloat = 45, + AddInt = 50, + AddFloat = 51, + SubInt = 52, + SubFloat = 53, + MulInt = 54, + MulFloat = 55, + DivInt = 56, + DivFloat = 57, + ModInt = 58, + EqualInt = 59, + EqualFloat = 60, + NotEqualInt = 61, + NotEqualFloat = 62, + LessInt = 63, + LessFloat = 64, + LessEqualInt = 65, + LessEqualFloat = 66, + GreaterInt = 67, + GreaterFloat = 68, + GreaterEqualInt = 69, + GreaterEqualFloat = 70, + NotInt = 71, + NotFloat = 72, + LogicalOr = 73, + LogicalAnd = 74, + BitwiseXor = 75, + BitwiseOr = 76, + BitwiseAnd = 77, + DecrementInt = 78, + Sin = 79, + Cos = 80, + CirclePos = 81, + ValidRad = 82, + Wait = 83, + NegateInt = 84, + NegateFloat = 85 , + SquareSum = 86, + GetAngle = 87, + SquareRoot = 88, + Linear = 89, + PointRotate = 90, + FloatTime = 91, + Math92 = 92, + Math93 = 93, + + EnemyCreate = 256, + EnemyCreateAbsolute = 257, + EnemySelectAnm = 258, + EnemySetAnmSprite = 259, + EnemyCreateMirrored = 260, + EnemyCreateMirroredAbsolute = 261, + EnemySetMainAnm = 262, + EnemyPlayAnm = 263, + EnemyPlayAnmAbsolute = 264, + EnemyCreateFiller = 265, + EnemyCreateFillerAbsolute = 266, + EnemyCreateFillerMirrored = 267, + EnemyCreateFillerMirroredAbsolute = 268, + EnemyPlaySelected = 269, + EnemySwitchAnm = 275, + EnemyResetAnm = 276, + EnemyInst277 = 277, + EnemyInst278 = 278, + + MovePos = 280, + MovePosTime = 281, + MovePosRel = 282, + MovePosRelTime = 283, + MoveVel = 284, + MoveVelTime = 285, + MoveVelRel = 286, + MoveVelRelTime = 287, + MoveCircle = 288, + MoveCircleTime = 289, + MoveCircleRel = 290, + MoveCircleRelTime = 291, + MoveRand = 292, + MoveRandRel = 293, + MoveAdd = 294, + MoveAddRel = 295, + MoveEllipse = 300, + MoveEllipseRel = 301, + MoveBezier = 305, + MoveReset = 307, + + SetHurtbox = 320, + SetHitbox = 321, + FlagSet = 322, + FlagClear = 323, + Movelimit = 324, + MoveLimitReset = 325, + ClearExtraDrops = 326, + AddExtraDrops = 327, + SetDropArea = 328, + DropItems = 329, + SetMainDrop = 330, + SetHealth = 331, + SetBoss = 332, + TimerReset = 333, + SetInterrupt = 334, + SetInvulnerable = 335, + PlaySound = 336, + ShakeScreen = 337, + StartDialogue = 338, + WaitForDialogue = 339, + WaitForDeath = 340, + SetTimeout = 341, + SpellById, + EndSpell, + SetChapter, + KillAllEnemies, + ProtectPlayer, + LifeMarker, + SetByDifficultyInt, + SetByDifficultyFloat, + SpellDifficulty, + SpellDifficultyM1, + +} diff --git a/src/game/states/game/mod.rs b/src/game/states/game/mod.rs new file mode 100644 index 0000000..08dec53 --- /dev/null +++ b/src/game/states/game/mod.rs @@ -0,0 +1,3 @@ +mod enemy; + +pub struct Game {} diff --git a/src/game/states/guard.rs b/src/game/states/guard.rs deleted file mode 100644 index cc5d577..0000000 --- a/src/game/states/guard.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::marker::PhantomData; - -use sfsm::TransitGuard; - -use crate::game::GameContext; - -pub enum TimeGuard { - Idle, - Waiting(u32), -} - -impl TimeGuard { - pub fn start_wait(&mut self, time: u32) { - *self = Self::Waiting(time); - } - - pub fn tick(&mut self) { - if let TimeGuard::Waiting(ref mut counter) = self { - *counter = counter.saturating_sub(1); - } - } - - pub fn guard(&self) -> TransitGuard { - matches!(self, TimeGuard::Waiting(0)).into() - } -} diff --git a/src/game/states/loading.rs b/src/game/states/loading.rs index 186262d..8e003aa 100644 --- a/src/game/states/loading.rs +++ b/src/game/states/loading.rs @@ -1,66 +1,55 @@ -use std::{ops::RangeInclusive, rc::Rc, sync::Arc}; +use std::{cell::RefCell, ops::RangeInclusive, sync::Arc}; use glam::Vec3; use sfsm::{State, TransitGuard, Transition}; -use winit::keyboard::KeyCode; -use crate::game::{ - anm::{Vm, VmLocation}, GameContext +use crate::{ + game::{ + anm::{LoadedFile, Vm, VmLocation}, GameContext + }, utils::soon::Soon }; -use super::{guard::TimeGuard, title::TitleScreen, UPDATE_CONTEXT}; +use super::{title::TitleScreen, UPDATE_CONTEXT}; pub struct Loading { - sig: Vm, - ascii_loading: Vm, - test_snowflakes: Vec, - time: u32, - range: RangeInclusive, - done: TimeGuard, + _sig: Vm, + _ascii_loading: Vm, + + title_anm: RefCell>>, } impl Loading { pub fn new(context: &mut GameContext) -> Loading { let ascii = context.load_anm("ascii.anm"); let sig_anm = context.load_anm("sig.anm"); - let sig = context.anm_manager.new_vm(sig_anm, None, false, false, 0, VmLocation::new_ui()); - let ascii_loading = context.anm_manager.new_vm( - ascii.clone(), - Some(Vec3::new(480.0, 392.0, 0.0)), - // Some(Vec3::new(300.0, 300.0, 0.0)), - false, - false, - 16, - VmLocation::new_ui(), - ); - // ascii_loading.borrow_mut().debug_freeze(); + let sig = context.anm_manager.new_vm(sig_anm, None, 0, VmLocation::new_ui()); + let ascii_loading = context.anm_manager.new_vm(ascii.clone(), None, 16, VmLocation::new_ui()); + ascii_loading.borrow_mut().origin = Vec3::new(480.0, 392.0, 0.0); Loading { - sig, - ascii_loading, - test_snowflakes: vec![], - time: 0, - range: f32::MAX..=f32::MIN, - done: TimeGuard::Idle, + _sig: sig, + _ascii_loading: ascii_loading, + + title_anm: context.start_load_anm("title.anm").into(), } - // LoadingState{ascii_loading} } } impl State for Loading { fn execute(&mut self) { - self.test_snowflakes.retain(|vm| !vm.borrow().deleted()); - self.time += 1; + // if UPDATE_CONTEXT.with(|context| context.sound_manager.get_bgm_manager().is_some()) { + // log::info!("a"); + // } + // if self.title_anm.borrow_mut().is_done() { + // log::info!("b") + // } } } impl Transition for Loading { fn guard(&self) -> TransitGuard { - // if self.time > 60 { - // TransitGuard::Transit - // } else { - // TransitGuard::Remain - // } - UPDATE_CONTEXT.with(|context| context.engine.keys.was_key_pressed(KeyCode::Space)).into() + UPDATE_CONTEXT + .with(|context| context.sound_manager.is_bgm_manager_loaded() && self.title_anm.borrow_mut().is_done()) + .into() } } diff --git a/src/game/states/mod.rs b/src/game/states/mod.rs index 84d9a9d..24213bf 100644 --- a/src/game/states/mod.rs +++ b/src/game/states/mod.rs @@ -1,4 +1,4 @@ -mod guard; +pub mod game; mod loading; mod title; @@ -10,7 +10,7 @@ use title::TitleScreen; use super::GameContext; -pub(super) static UPDATE_CONTEXT: ContextMut = ContextMut::new(); +pub(super) static UPDATE_CONTEXT: ContextMut = ContextMut::new(); add_state_machine!(Machine, Loading, {Loading, TitleScreen}, { Loading => TitleScreen, diff --git a/src/game/states/title.rs b/src/game/states/title.rs index dcef7e5..f19f2cb 100644 --- a/src/game/states/title.rs +++ b/src/game/states/title.rs @@ -1,4 +1,4 @@ -use sfsm::{State, Transition}; +use sfsm::{State, TransitGuard, Transition}; use winit::keyboard::KeyCode; use crate::game::{ @@ -17,8 +17,8 @@ impl TitleScreen { let title = context.load_anm("title.anm"); // 79, 83 - let splash = context.anm_manager.new_vm(title.clone(), None, false, false, 79, VmLocation::new_ui()); - let logo = context.anm_manager.new_vm(title.clone(), None, false, true, 83, VmLocation::new_ui()); + let splash = context.anm_manager.new_vm(title.clone(), Some(8), 79, VmLocation::new_ui()); + let logo = context.anm_manager.new_vm(title.clone(), Some(8), 83, VmLocation::new_ui()); Self { _logo: logo, @@ -28,20 +28,31 @@ impl TitleScreen { } impl State for TitleScreen { - fn execute(&mut self) {} + fn execute(&mut self) { + UPDATE_CONTEXT.with(|context| { + if context.engine.keys.was_key_pressed(KeyCode::Space) { + self._logo.borrow_mut().interrupt(1); + } + }) + } } impl From for TitleScreen { fn from(_: Loading) -> Self { - UPDATE_CONTEXT.with(|context| TitleScreen::new(context, true)) + UPDATE_CONTEXT.with(|context| { + context.sound_manager.get_bgm_manager().play_track("th11_00.wav"); + TitleScreen::new(context, true) + }) } } impl Transition for TitleScreen { fn action(&mut self) { - *self = UPDATE_CONTEXT.with(|context| TitleScreen::new(context, true)) + *self = UPDATE_CONTEXT.with(|context| TitleScreen::new(context, false)) } - fn guard(&self) -> sfsm::TransitGuard { + + fn guard(&self) -> TransitGuard { UPDATE_CONTEXT.with(|context| context.engine.keys.was_key_pressed(KeyCode::Space)).into() + // UPDATE_CONTEXT.with(|_| self._logo.borrow().deleted()).into() } } diff --git a/src/main.rs b/src/main.rs index f547f8a..d1c4c60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,9 @@ pub mod engine; pub mod game; pub mod interp; -pub mod utils { - pub mod context; -} +pub mod utils; -use std::{ - sync::Arc, time::{Duration, Instant} -}; +use std::{sync::Arc, time::Duration}; use engine::Engine; use wgpu::SurfaceError; @@ -43,11 +39,11 @@ impl<'a> ApplicationHandler for App<'a> { std::process::exit(0); } - fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + fn about_to_wait(&mut self, _: &winit::event_loop::ActiveEventLoop) { // self.window.as_ref().unwrap().request_redraw(); } - fn new_events(&mut self, event_loop: &winit::event_loop::ActiveEventLoop, cause: winit::event::StartCause) { + fn new_events(&mut self, _: &winit::event_loop::ActiveEventLoop, cause: winit::event::StartCause) { if let StartCause::ResumeTimeReached { .. } = cause { self.window.as_ref().unwrap().request_redraw(); } diff --git a/src/utils/context.rs b/src/utils/context.rs index 22d8fb0..3437d5a 100644 --- a/src/utils/context.rs +++ b/src/utils/context.rs @@ -65,13 +65,13 @@ use std::marker::PhantomData; #[doc(hidden)] #[derive(Default)] -pub struct ContextMut(PhantomData); +pub struct ContextMut(PhantomData<(T, Key)>); -unsafe impl Sync for ContextMut {} +unsafe impl Sync for ContextMut {} #[allow(unused)] -impl ContextMut { - pub const fn new() -> ContextMut { +impl ContextMut { + pub const fn new() -> ContextMut { ContextMut(PhantomData) } @@ -107,13 +107,13 @@ impl ContextMut { #[doc(hidden)] #[derive(Default)] -pub struct Context(PhantomData); +pub struct Context(PhantomData<(T, Key)>); -unsafe impl Sync for Context {} +unsafe impl Sync for Context {} #[allow(unused)] -impl Context { - pub const fn new() -> Context { +impl Context { + pub const fn new() -> Context { Context(PhantomData) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..289ab50 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod context; +pub mod soon; diff --git a/src/utils/soon.rs b/src/utils/soon.rs new file mode 100644 index 0000000..1bc2e71 --- /dev/null +++ b/src/utils/soon.rs @@ -0,0 +1,70 @@ +use std::future::Future; + +use futures::channel::oneshot; + +pub enum Soon { + Waiting(oneshot::Receiver), + Value(T), +} + +impl Soon { + pub fn new(future: impl Future + Send + 'static) -> Self { + let (sender, receiver) = oneshot::channel(); + async_std::task::spawn(async move { + let _ = sender.send(future.await); + }); + + Self::Waiting(receiver) + } + + pub fn is_done(&mut self) -> bool { + match self { + Soon::Waiting(receiver) => { + if let Some(value) = receiver.try_recv().unwrap() { + *self = Soon::Value(value); + + true + } else { + false + } + } + Soon::Value(_) => true, + } + } + + pub fn try_borrow(&mut self) -> Option<&T> { + match self { + Soon::Waiting(receiver) => { + if let Some(value) = receiver.try_recv().unwrap() { + *self = Soon::Value(value); + + self.try_borrow() + } else { + None + } + } + Soon::Value(ref value) => Some(value), + } + } + + pub fn try_borrow_mut(&mut self) -> Option<&mut T> { + match self { + Soon::Waiting(receiver) => { + if let Some(value) = receiver.try_recv().unwrap() { + *self = Soon::Value(value); + + self.try_borrow_mut() + } else { + None + } + } + Soon::Value(ref mut value) => Some(value), + } + } +} + +impl Soon { + pub fn try_clone(&mut self) -> Option { + self.try_borrow().cloned() + } +}