use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct SceneEntry {
pub id: i64,
pub name: String,
pub attributes: HashMap<String, String>,
pub fn_path: String,
pub fn_name: String,
pub parent: Option<String>,
}
pub struct SceneRegistry {
scenes: Vec<SceneEntry>,
name_counters: HashMap<String, usize>,
}
impl SceneRegistry {
pub fn new() -> Self {
Self {
scenes: Vec::new(),
name_counters: HashMap::new(),
}
}
pub fn register_global(
&mut self,
name: &str,
attributes: HashMap<String, String>,
) -> (i64, usize) {
let counter = self.increment_counter(name);
let id = (self.scenes.len() + 1) as i64;
let fn_name = format!("{}_{}::__start__", Self::sanitize_name(name), counter);
let fn_path = format!("crate::{}", fn_name);
let entry = SceneEntry {
id,
name: name.to_string(),
attributes,
fn_path,
fn_name,
parent: None,
};
self.scenes.push(entry);
(id, counter)
}
pub fn register_local(
&mut self,
name: &str,
parent_name: &str,
parent_counter: usize,
local_index: usize,
attributes: HashMap<String, String>,
) -> i64 {
let id = (self.scenes.len() + 1) as i64;
let fn_name = format!(
"{}_{}::{}_{}",
Self::sanitize_name(parent_name),
parent_counter,
Self::sanitize_name(name),
local_index
);
let fn_path = format!("crate::{}", fn_name);
let entry = SceneEntry {
id,
name: name.to_string(),
attributes,
fn_path,
fn_name,
parent: Some(parent_name.to_string()),
};
self.scenes.push(entry);
id
}
pub fn register_global_raw(
&mut self,
full_name: &str,
local_names: &[String],
attributes: HashMap<String, String>,
) -> i64 {
let id = (self.scenes.len() + 1) as i64;
let fn_name = format!("{}::__start__", full_name);
let fn_path = format!("crate::{}", fn_name);
let entry = SceneEntry {
id,
name: full_name.to_string(),
attributes: attributes.clone(),
fn_path,
fn_name,
parent: None,
};
self.scenes.push(entry);
let global_id = id;
for local_name in local_names.iter() {
if local_name != "__start__" {
let local_id = (self.scenes.len() + 1) as i64;
let local_fn_name = format!("{}::{}", full_name, local_name);
let local_fn_path = format!("crate::{}", local_fn_name);
let local_entry = SceneEntry {
id: local_id,
name: local_name.clone(),
attributes: attributes.clone(),
fn_path: local_fn_path,
fn_name: local_fn_name,
parent: Some(full_name.to_string()),
};
self.scenes.push(local_entry);
}
}
global_id
}
pub fn all_scenes(&self) -> Vec<&SceneEntry> {
self.scenes.iter().collect()
}
pub fn get_scene(&self, id: i64) -> Option<&SceneEntry> {
if id < 1 {
return None;
}
self.scenes.get((id - 1) as usize)
}
fn increment_counter(&mut self, name: &str) -> usize {
let counter = self.name_counters.entry(name.to_string()).or_insert(0);
*counter += 1;
*counter
}
pub fn sanitize_name(name: &str) -> String {
name.replace(|c: char| !c.is_alphanumeric() && c != '_', "_")
}
pub fn merge_from(&mut self, other: SceneRegistry) {
for mut entry in other.scenes {
entry.id = (self.scenes.len() + 1) as i64;
self.scenes.push(entry);
}
for (name, counter) in other.name_counters {
let current = self.name_counters.entry(name).or_insert(0);
if counter > *current {
*current = counter;
}
}
}
}
impl Default for SceneRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_global_label() {
let mut registry = SceneRegistry::new();
let (id1, counter1) = registry.register_global("会話", HashMap::new());
assert_eq!(id1, 1);
assert_eq!(counter1, 1);
let scene = registry.get_scene(id1).unwrap();
assert_eq!(scene.name, "会話");
assert_eq!(scene.fn_path, "crate::会話_1::__start__");
assert_eq!(scene.parent, None);
}
#[test]
fn test_register_multiple_global_labels() {
let mut registry = SceneRegistry::new();
let (id1, _) = registry.register_global("会話", HashMap::new());
let (id2, _) = registry.register_global("別会話", HashMap::new());
assert_eq!(id1, 1);
assert_eq!(id2, 2);
let scenes = registry.all_scenes();
assert_eq!(scenes.len(), 2);
assert_eq!(scenes[0].name, "会話");
assert_eq!(scenes[1].name, "別会話");
}
#[test]
fn test_register_duplicate_global_labels() {
let mut registry = SceneRegistry::new();
let (id1, counter1) = registry.register_global("会話", HashMap::new());
let (id2, counter2) = registry.register_global("会話", HashMap::new());
assert_eq!(id1, 1);
assert_eq!(counter1, 1);
assert_eq!(id2, 2);
assert_eq!(counter2, 2);
let label1 = registry.get_scene(id1).unwrap();
let label2 = registry.get_scene(id2).unwrap();
assert_eq!(label1.fn_path, "crate::会話_1::__start__");
assert_eq!(label2.fn_path, "crate::会話_2::__start__");
}
#[test]
fn test_register_local_label() {
let mut registry = SceneRegistry::new();
let (parent_id, parent_counter) = registry.register_global("会話", HashMap::new());
let local_id = registry.register_local("選択肢", "会話", parent_counter, 1, HashMap::new());
assert_eq!(parent_id, 1);
assert_eq!(local_id, 2);
let local_label = registry.get_scene(local_id).unwrap();
assert_eq!(local_label.name, "選択肢");
assert_eq!(local_label.parent, Some("会話".to_string()));
assert_eq!(local_label.fn_path, "crate::会話_1::選択肢_1");
}
#[test]
fn test_sanitize_name() {
assert_eq!(SceneRegistry::sanitize_name("hello"), "hello");
assert_eq!(SceneRegistry::sanitize_name("hello-world"), "hello_world");
assert_eq!(SceneRegistry::sanitize_name("会話"), "会話");
assert_eq!(SceneRegistry::sanitize_name("*会話"), "_会話");
}
}