summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock66
-rw-r--r--ecs/Cargo.toml4
-rw-r--r--ecs/src/component/storage.rs170
-rw-r--r--ecs/src/component/storage/graph.rs12
-rw-r--r--ecs/src/lib.rs53
5 files changed, 303 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b7df585..625d625 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -42,6 +42,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
name = "bindgen"
version = "0.68.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -56,7 +62,27 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
- "rustc-hash",
+ "rustc-hash 1.1.0",
+ "shlex",
+ "syn",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.71.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
+dependencies = [
+ "bitflags 2.9.0",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash 2.1.1",
"shlex",
"syn",
]
@@ -238,6 +264,7 @@ dependencies = [
"thiserror",
"tracing",
"util-macros",
+ "vizoxide",
]
[[package]]
@@ -336,7 +363,7 @@ dependencies = [
name = "glfw"
version = "0.1.0"
dependencies = [
- "bindgen",
+ "bindgen 0.68.1",
"bitflags 2.9.0",
"libc",
"thiserror",
@@ -350,6 +377,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
+name = "graphviz-sys"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97dbac5b269c7160b78812a9873f548668aa34b62f28f7a64328a6cd94feb47d"
+dependencies = [
+ "bindgen 0.71.1",
+]
+
+[[package]]
name = "half"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -635,6 +671,16 @@ dependencies = [
]
[[package]]
+name = "prettyplease"
+version = "0.2.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -712,6 +758,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -955,6 +1007,16 @@ dependencies = [
]
[[package]]
+name = "vizoxide"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec190047a5e02a6423f147cf40890be1d37bbc68b9eba441689acbcd255f942"
+dependencies = [
+ "base64",
+ "graphviz-sys",
+]
+
+[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml
index 4945171..cf35a74 100644
--- a/ecs/Cargo.toml
+++ b/ecs/Cargo.toml
@@ -3,6 +3,9 @@ name = "ecs"
version = "0.1.0"
edition = "2021"
+[features]
+vizoxide = ["dep:vizoxide"]
+
[dependencies]
seq-macro = "0.3.5"
paste = "1.0.14"
@@ -13,6 +16,7 @@ hashbrown = "0.15.2"
parking_lot = "0.12.3"
ecs-macros = { path = "../ecs-macros" }
util-macros = { path = "../util-macros" }
+vizoxide = { version = "1.0.5", optional = true }
[dev-dependencies.criterion]
version = "0.5.1"
diff --git a/ecs/src/component/storage.rs b/ecs/src/component/storage.rs
index 4b2b6d8..c70e7e7 100644
--- a/ecs/src/component/storage.rs
+++ b/ecs/src/component/storage.rs
@@ -421,6 +421,176 @@ impl TypeName for Storage
}
}
+#[cfg(feature = "vizoxide")]
+impl Storage
+{
+ pub fn create_vizoxide_archetype_graph(
+ &self,
+ graph_name: impl AsRef<str>,
+ params: VizoxideArchetypeGraphParams,
+ ) -> Result<vizoxide::Graph, vizoxide::GraphvizError>
+ {
+ let viz_graph = vizoxide::Graph::builder(graph_name.as_ref())
+ .strict(true)
+ .directed(true)
+ .build()?;
+
+ let mut viz_node_lookup = HashMap::new();
+
+ for node in self.graph.iter_nodes() {
+ let id = node.archetype().id();
+
+ if !viz_node_lookup.contains_key(&id) {
+ let node = self.graph.get_node_by_id(id).unwrap();
+
+ let viz_node = (params.create_node_cb)(
+ node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ viz_graph.create_node(&(params.create_node_name)(
+ node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ )),
+ )
+ .build()?;
+
+ viz_node_lookup.insert(id, viz_node);
+ }
+
+ for (edge_comp_id, edges) in node.iter_edges() {
+ if let Some(add_edge) = edges.add {
+ if !viz_node_lookup.contains_key(&add_edge) {
+ let viz_node = self.create_vizoxide_archetype_graph_edge_node(
+ &viz_graph,
+ node,
+ add_edge,
+ *edge_comp_id,
+ &params,
+ )?;
+
+ viz_node_lookup.insert(add_edge, viz_node);
+ }
+
+ (params.create_edge_cb)(
+ node.archetype(),
+ *edge_comp_id,
+ VizoxideArchetypeGraphEdgeKind::Add,
+ viz_graph.create_edge(
+ viz_node_lookup.get(&id).unwrap(),
+ viz_node_lookup.get(&add_edge).unwrap(),
+ Some(&format!("Add {}", edge_comp_id.id())),
+ ),
+ )
+ .build()?;
+ }
+
+ if let Some(remove_edge) = edges.remove {
+ if !viz_node_lookup.contains_key(&remove_edge) {
+ let viz_node = self.create_vizoxide_archetype_graph_edge_node(
+ &viz_graph,
+ node,
+ remove_edge,
+ *edge_comp_id,
+ &params,
+ )?;
+
+ viz_node_lookup.insert(remove_edge, viz_node);
+ }
+
+ (params.create_edge_cb)(
+ node.archetype(),
+ *edge_comp_id,
+ VizoxideArchetypeGraphEdgeKind::Remove,
+ viz_graph.create_edge(
+ viz_node_lookup.get(&id).unwrap(),
+ viz_node_lookup.get(&remove_edge).unwrap(),
+ Some(&format!("Remove {}", edge_comp_id.id())),
+ ),
+ )
+ .build()?;
+ }
+ }
+ }
+
+ drop(viz_node_lookup);
+
+ Ok(viz_graph)
+ }
+
+ fn create_vizoxide_archetype_graph_edge_node<'vizoxide_graph>(
+ &self,
+ viz_graph: &'vizoxide_graph vizoxide::Graph,
+ node: &graph::ArchetypeNode,
+ edge_id: ArchetypeId,
+ edge_comp_id: Uid,
+ params: &VizoxideArchetypeGraphParams,
+ ) -> Result<vizoxide::Node<'vizoxide_graph>, vizoxide::GraphvizError>
+ {
+ match self.graph.get_node_by_id(edge_id) {
+ Some(edge_node) => (params.create_node_cb)(
+ edge_node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ viz_graph.create_node(&(params.create_node_name)(
+ edge_node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ )),
+ )
+ .build(),
+ None => {
+ let mut comp_ids =
+ node.archetype().component_ids_sorted().collect::<Vec<_>>();
+
+ let insert_index = comp_ids.partition_point(|cid| *cid <= edge_comp_id);
+
+ comp_ids.insert(insert_index, edge_comp_id);
+
+ let imaginary_edge_archetype = Archetype::new(edge_id, comp_ids);
+
+ (params.create_node_cb)(
+ &imaginary_edge_archetype,
+ ArchetypeMetadata { is_imaginary: true },
+ viz_graph.create_node(&(params.create_node_name)(
+ &imaginary_edge_archetype,
+ ArchetypeMetadata { is_imaginary: true },
+ )),
+ )
+ .build()
+ }
+ }
+ }
+}
+
+#[cfg(feature = "vizoxide")]
+pub struct VizoxideArchetypeGraphParams
+{
+ pub create_node_name: fn(&Archetype, ArchetypeMetadata) -> std::borrow::Cow<'_, str>,
+ pub create_node_cb: for<'storage, 'graph> fn(
+ &'storage Archetype,
+ ArchetypeMetadata,
+ vizoxide::NodeBuilder<'graph>,
+ ) -> vizoxide::NodeBuilder<'graph>,
+ pub create_edge_cb: for<'storage, 'graph> fn(
+ &'storage Archetype,
+ Uid,
+ VizoxideArchetypeGraphEdgeKind,
+ vizoxide::EdgeBuilder<'graph>,
+ ) -> vizoxide::EdgeBuilder<'graph>,
+}
+
+#[cfg(feature = "vizoxide")]
+#[derive(Debug, Clone)]
+pub struct ArchetypeMetadata
+{
+ pub is_imaginary: bool,
+}
+
+#[cfg(feature = "vizoxide")]
+#[derive(Debug, Clone, Copy)]
+pub enum VizoxideArchetypeGraphEdgeKind
+{
+ Add,
+ Remove,
+}
+
#[derive(Debug)]
pub struct ArchetypeRefIter<'storage, 'search_terms>
{
diff --git a/ecs/src/component/storage/graph.rs b/ecs/src/component/storage/graph.rs
index a221877..11160e7 100644
--- a/ecs/src/component/storage/graph.rs
+++ b/ecs/src/component/storage/graph.rs
@@ -71,6 +71,12 @@ impl Graph
}))
}
+ #[cfg(feature = "vizoxide")]
+ pub fn iter_nodes(&self) -> impl Iterator<Item = &ArchetypeNode>
+ {
+ self.nodes.iter()
+ }
+
pub fn dfs_archetype_add_edges(
&self,
archetype_id: ArchetypeId,
@@ -233,6 +239,12 @@ impl ArchetypeNode
self.edges.entry(component_id).or_insert_with(insert_fn)
}
+ #[cfg(feature = "vizoxide")]
+ pub fn iter_edges(&self) -> impl Iterator<Item = (&Uid, &ArchetypeEdges)>
+ {
+ self.edges.iter()
+ }
+
pub fn get_edges_mut(&mut self, component_id: Uid) -> Option<&mut ArchetypeEdges>
{
debug_assert_eq!(component_id.kind(), UidKind::Component);
diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs
index 9b787a2..3adc415 100644
--- a/ecs/src/lib.rs
+++ b/ecs/src/lib.rs
@@ -246,6 +246,59 @@ impl World
while let StepResult::Continue = self.step() {}
}
+ #[cfg(feature = "vizoxide")]
+ pub fn create_vizoxide_archetype_graph(
+ &self,
+ name: impl AsRef<str>,
+ ) -> Result<vizoxide::Graph, vizoxide::GraphvizError>
+ {
+ use std::borrow::Cow;
+
+ use crate::component::storage::{
+ VizoxideArchetypeGraphEdgeKind,
+ VizoxideArchetypeGraphParams,
+ };
+
+ let component_storage_lock = self
+ .data
+ .component_storage
+ .read_nonblock()
+ .expect("Failed to acquire read-only component storage lock");
+
+ component_storage_lock.create_vizoxide_archetype_graph(
+ name,
+ VizoxideArchetypeGraphParams {
+ create_node_name: |archetype, _| {
+ Cow::Owned(format!(
+ "[{}]",
+ archetype
+ .component_ids_sorted()
+ .into_iter()
+ .map(|comp_id| comp_id.id().to_string())
+ .collect::<Vec<_>>()
+ .join(", ")
+ ))
+ },
+ create_node_cb: |_archetype, archetype_metadata, node_builder| {
+ if archetype_metadata.is_imaginary {
+ return node_builder.attribute("shape", "ellipse");
+ }
+
+ node_builder.attribute("shape", "box")
+ },
+ create_edge_cb: |_, _, edge_kind, edge_builder| {
+ edge_builder.attribute(
+ "color",
+ match edge_kind {
+ VizoxideArchetypeGraphEdgeKind::Add => "green",
+ VizoxideArchetypeGraphEdgeKind::Remove => "red",
+ },
+ )
+ },
+ },
+ )
+ }
+
fn query_and_run_systems(&self, phase_euid: Uid)
{
let system_comps_query =