diff --git a/cosmos_core/src/events/block_events.rs b/cosmos_core/src/events/block_events.rs index 63d71acb..e5d420ad 100644 --- a/cosmos_core/src/events/block_events.rs +++ b/cosmos_core/src/events/block_events.rs @@ -10,7 +10,7 @@ use bevy::prelude::App; use bevy::prelude::Entity; use bevy::prelude::Event; -#[derive(Debug, Event)] +#[derive(Debug, Event, Clone)] /// Sent when a block is changed (destroyed or placed) /// /// This is NOT SENT when a block's data is modified. diff --git a/cosmos_core/src/logic/logic_driver.rs b/cosmos_core/src/logic/logic_driver.rs index 8a41c056..bd81bca1 100644 --- a/cosmos_core/src/logic/logic_driver.rs +++ b/cosmos_core/src/logic/logic_driver.rs @@ -3,11 +3,12 @@ use bevy::{ prelude::{Component, Entity, EventWriter}, reflect::Reflect, - utils::HashSet, + utils::{HashMap, HashSet}, }; use crate::{ block::{block_direction::BlockDirection, block_face::ALL_BLOCK_FACES, block_rotation::BlockRotation, Block}, + events::block_events::BlockChangedEvent, logic::LogicConnection, registry::Registry, structure::{coordinates::BlockCoordinate, structure_block::StructureBlock, Structure}, @@ -45,6 +46,7 @@ impl LogicDriver { port_type: PortType, structure: &Structure, entity: Entity, + events_by_coords: &HashMap, blocks: &Registry, logic_blocks: &Registry, evw_queue_logic_output: &mut EventWriter, @@ -63,6 +65,7 @@ impl LogicDriver { None, false, structure, + events_by_coords, &mut Port::all_for(coords), blocks, logic_blocks, @@ -89,6 +92,7 @@ impl LogicDriver { coords: BlockCoordinate, structure: &Structure, entity: Entity, + events_by_coords: &HashMap, blocks: &Registry, logic_blocks: &Registry, logic_wire_colors: &Registry, @@ -103,6 +107,7 @@ impl LogicDriver { PortType::Input, structure, entity, + events_by_coords, blocks, logic_blocks, evw_queue_logic_output, @@ -118,6 +123,7 @@ impl LogicDriver { PortType::Output, structure, entity, + events_by_coords, blocks, logic_blocks, evw_queue_logic_output, @@ -142,6 +148,7 @@ impl LogicDriver { Some(wire_color_id), logic_block.connection_on(wire_face) == Some(crate::logic::LogicConnection::Wire(WireType::Bus)), structure, + events_by_coords, &mut Port::all_for(coords), blocks, logic_blocks, @@ -173,6 +180,7 @@ impl LogicDriver { coords: BlockCoordinate, structure: &Structure, entity: Entity, + events_by_coords: &HashMap, blocks: &Registry, logic_blocks: &Registry, logic_wire_colors: &Registry, @@ -186,6 +194,9 @@ impl LogicDriver { rotation.direction_of(input_face), PortType::Input, structure, + events_by_coords, + blocks, + logic_blocks, evw_queue_logic_input, ) } @@ -197,6 +208,9 @@ impl LogicDriver { rotation.direction_of(output_face), PortType::Output, structure, + events_by_coords, + blocks, + logic_blocks, evw_queue_logic_input, ) } @@ -204,9 +218,15 @@ impl LogicDriver { // A block with no wire faces will have an empty wire face colors iterator. for wire_color_id in logic_block.wire_face_colors(logic_wire_colors) { // Old group ID either comes from being the stored wire coordinate for a group, or searching all your neighbors. - let old_group_id = self - .logic_graph - .get_wire_group(coords, wire_color_id, logic_block, structure, blocks, logic_blocks); + let old_group_id = self.logic_graph.get_wire_group( + coords, + wire_color_id, + logic_block, + structure, + events_by_coords, + blocks, + logic_blocks, + ); let was_on = self.logic_graph.get_group(old_group_id).on(); // Setting new group IDs. @@ -225,6 +245,7 @@ impl LogicDriver { wire_color_id, logic_block.connection_on(wire_face) == Some(LogicConnection::Wire(WireType::Bus)), structure, + events_by_coords, &mut visited, blocks, logic_blocks, diff --git a/cosmos_core/src/logic/logic_graph.rs b/cosmos_core/src/logic/logic_graph.rs index e33cb12b..ca236a4f 100644 --- a/cosmos_core/src/logic/logic_graph.rs +++ b/cosmos_core/src/logic/logic_graph.rs @@ -8,6 +8,7 @@ use bevy::{ use crate::{ block::{block_direction::BlockDirection, Block}, + events::block_events::BlockChangedEvent, registry::{identifiable::Identifiable, Registry}, structure::{coordinates::BlockCoordinate, structure_block::StructureBlock, Structure}, }; @@ -86,10 +87,6 @@ impl LogicGroup { ); } } - - pub(crate) fn has_one_port(&self) -> bool { - (self.consumers.len() + self.producers.len()) == 1 - } } #[derive(Debug, Default, Reflect)] @@ -158,6 +155,23 @@ impl LogicGraph { })) } + /// `LogicGraph`'s `dfs_for_group` method needs to know which blocks are at each coordinate to properly search the graph. + /// + /// However, several `BlockChangedEvent`s can occur on the same tick. + /// We use `events_by_coords` to track all the events the logic graph has alredy processed this tick, and pretend the blocks have already been changed in the structure. + fn block_at<'a>( + &self, + coords: BlockCoordinate, + structure: &'a Structure, + events_by_coords: &HashMap, + blocks: &'a Registry, + ) -> &'a Block { + if let Some(ev) = events_by_coords.get(&coords) { + return blocks.from_numeric_id(ev.new_block); + } + structure.block_at(coords, blocks) + } + pub fn dfs_for_group( &self, coords: BlockCoordinate, @@ -165,11 +179,12 @@ impl LogicGraph { mut required_color_id: Option, from_bus: bool, structure: &Structure, + events_by_coords: &HashMap, visited: &mut HashSet, blocks: &Registry, logic_blocks: &Registry, ) -> Option { - let block = structure.block_at(coords, blocks); + let block = self.block_at(coords, structure, events_by_coords, blocks); let Some(logic_block) = logic_blocks.from_id(block.unlocalized_name()) else { // Not a logic block. return None; @@ -232,6 +247,7 @@ impl LogicGraph { Some(wire_color_id), wire_type == WireType::Bus, structure, + events_by_coords, visited, blocks, logic_blocks, @@ -255,6 +271,7 @@ impl LogicGraph { wire_color_id: u16, coords: BlockCoordinate, structure: &Structure, + events_by_coords: &HashMap, visited: &mut HashSet, blocks: &Registry, logic_blocks: &Registry, @@ -270,6 +287,7 @@ impl LogicGraph { Some(wire_color_id), logic_block.connection_on(wire_face) == Some(LogicConnection::Wire(WireType::Bus)), structure, + events_by_coords, visited, blocks, logic_blocks, @@ -286,6 +304,7 @@ impl LogicGraph { wire_color_id: u16, logic_block: &LogicBlock, structure: &Structure, + events_by_coords: &HashMap, blocks: &Registry, logic_blocks: &Registry, ) -> usize { @@ -304,6 +323,7 @@ impl LogicGraph { wire_color_id, coords, structure, + events_by_coords, &mut Port::all_for(coords), blocks, logic_blocks, @@ -365,22 +385,38 @@ impl LogicGraph { direction: BlockDirection, port_type: PortType, structure: &Structure, + events_by_coords: &HashMap, + blocks: &Registry, + logic_blocks: &Registry, evw_queue_logic_input: &mut EventWriter, ) { + // If the neighbor coordinates don't exist, no port is removed. + let Ok(neighbor_coords) = coords.step(direction) else { + return; + }; + let port = Port::new(coords, direction); let &group_id = match port_type { PortType::Input => &mut self.input_port_group_id, PortType::Output => &mut self.output_port_group_id, } .get(&port) - .unwrap_or_else(|| panic!("Port {port:?} to be removed should have a logic group.")); + .expect("Port to be removed should exist."); // Check if this port is the last block of its group, and delete the group if so. if self - .groups - .get(&group_id) - .expect("Logic group with port should exist.") - .has_one_port() + .dfs_for_group( + neighbor_coords, + direction.inverse(), + None, + false, + structure, + events_by_coords, + &mut Port::all_for(coords), + blocks, + logic_blocks, + ) + .is_none() { self.remove_group(group_id); } else { @@ -480,6 +516,7 @@ impl LogicGraph { wire_color_id: u16, from_bus: bool, structure: &Structure, + events_by_coords: &HashMap, visited: &mut HashSet, blocks: &Registry, logic_blocks: &Registry, @@ -490,7 +527,7 @@ impl LogicGraph { // Renaming on this portion already completed. return false; } - let block = structure.block_at(coords, blocks); + let block = self.block_at(coords, structure, events_by_coords, blocks); let Some(logic_block) = logic_blocks.from_id(block.unlocalized_name()) else { // Not a logic block. return false; @@ -553,6 +590,7 @@ impl LogicGraph { wire_color_id, wire_type == WireType::Bus, structure, + events_by_coords, visited, blocks, logic_blocks, diff --git a/cosmos_core/src/logic/mod.rs b/cosmos_core/src/logic/mod.rs index f0c599a7..c0c3824d 100644 --- a/cosmos_core/src/logic/mod.rs +++ b/cosmos_core/src/logic/mod.rs @@ -10,7 +10,7 @@ use bevy::{ }, reflect::Reflect, time::common_conditions::on_timer, - utils::HashSet, + utils::{HashMap, HashSet}, }; use logic_driver::LogicDriver; use logic_graph::{LogicGraph, LogicGroup}; @@ -344,7 +344,7 @@ fn listen_for_changed_logic_data( ); } -fn logic_block_placed_event_listener( +fn logic_block_changed_event_listener( mut evr_block_changed: EventReader, blocks: Res>, logic_blocks: Res>, @@ -357,48 +357,59 @@ fn logic_block_placed_event_listener( mut evw_queue_logic_output: EventWriter, mut evw_queue_logic_input: EventWriter, ) { - for ev in evr_block_changed.read() { - // If was logic block, remove from the logic graph. - if let Some(logic_block) = logic_blocks.from_id(blocks.from_numeric_id(ev.old_block).unlocalized_name()) { - if let Ok(structure) = q_structure.get_mut(ev.block.structure()) { - if let Ok(mut logic) = q_logic.get_mut(ev.block.structure()) { - logic.remove_logic_block( - logic_block, - ev.old_block_rotation(), - ev.block.coords(), - &structure, - structure.get_entity().expect("Structure should have entity."), - &blocks, - &logic_blocks, - &logic_wire_colors, - &mut evw_queue_logic_output, - &mut evw_queue_logic_input, - ) + // We group the events by entity so we can track the block changes the previous events made. + let entities = evr_block_changed.read().map(|ev| ev.block.structure()).collect::>(); + for entity in entities { + let current_entity_events = evr_block_changed.read().filter(|ev| ev.block.structure() == entity); + let mut events_by_coords: HashMap = HashMap::new(); + for ev in current_entity_events { + // If was logic block, remove from the logic graph. + if let Some(logic_block) = logic_blocks.from_id(blocks.from_numeric_id(ev.old_block).unlocalized_name()) { + if let Ok(structure) = q_structure.get_mut(ev.block.structure()) { + if let Ok(mut logic) = q_logic.get_mut(ev.block.structure()) { + logic.remove_logic_block( + logic_block, + ev.old_block_rotation(), + ev.block.coords(), + &structure, + entity, + &events_by_coords, + &blocks, + &logic_blocks, + &logic_wire_colors, + &mut evw_queue_logic_output, + &mut evw_queue_logic_input, + ) + } } } - } - // If is now logic block, add to the logic graph. - if let Some(logic_block) = logic_blocks.from_id(blocks.from_numeric_id(ev.new_block).unlocalized_name()) { - if let Ok(mut structure) = q_structure.get_mut(ev.block.structure()) { - if let Ok(mut logic) = q_logic.get_mut(ev.block.structure()) { - let coords = ev.block.coords(); - logic.add_logic_block( - logic_block, - ev.new_block_rotation(), - coords, - &structure, - structure.get_entity().expect("Structure should have entity"), - &blocks, - &logic_blocks, - &logic_wire_colors, - &mut evw_queue_logic_output, - &mut evw_queue_logic_input, - ); - // Add the logic block's internal data storage to the structure. - structure.insert_block_data(coords, BlockLogicData(0), &mut bs_params, &mut q_block_data, &q_has_data); + // If is now logic block, add to the logic graph. + if let Some(logic_block) = logic_blocks.from_id(blocks.from_numeric_id(ev.new_block).unlocalized_name()) { + if let Ok(mut structure) = q_structure.get_mut(ev.block.structure()) { + if let Ok(mut logic) = q_logic.get_mut(ev.block.structure()) { + let coords = ev.block.coords(); + logic.add_logic_block( + logic_block, + ev.new_block_rotation(), + coords, + &structure, + entity, + &events_by_coords, + &blocks, + &logic_blocks, + &logic_wire_colors, + &mut evw_queue_logic_output, + &mut evw_queue_logic_input, + ); + // Add the logic block's internal data storage to the structure. + structure.insert_block_data(coords, BlockLogicData(0), &mut bs_params, &mut q_block_data, &q_has_data); + } } } + + // Add the event we just processed to the HashMap so we can pretend the structure was updated in the coming iterations DFS. + events_by_coords.insert(ev.block.coords(), ev.clone()); } } } @@ -540,7 +551,7 @@ pub(super) fn register(app: &mut App, playing_state: T) { Update, ( add_default_logic.in_set(StructureLoadingSet::AddStructureComponents), - logic_block_placed_event_listener.in_set(LogicSystemSet::EditLogicGraph), + logic_block_changed_event_listener.in_set(LogicSystemSet::EditLogicGraph), queue_logic_producers.in_set(LogicSystemSet::QueueProducers), queue_logic_consumers.in_set(LogicSystemSet::QueueConsumers), send_queued_logic_events.in_set(LogicSystemSet::SendQueues),