refactor(vm) helpful codegen round 1~

This commit is contained in:
Rachel Powers
2024-05-07 21:59:06 -07:00
parent 2d8b35c5b2
commit 5cc935b826
49 changed files with 21966 additions and 3999 deletions

28
xtask/src/enums.rs Normal file
View File

@@ -0,0 +1,28 @@
use std::collections::BTreeMap;
use serde_derive::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(rename = "Enums")]
pub struct Enums {
#[serde(rename = "scriptEnums")]
pub script_enums: BTreeMap<String, EnumListing>,
#[serde(rename = "basicEnums")]
pub basic_enums: BTreeMap<String, EnumListing>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(rename = "EnumListing")]
pub struct EnumListing {
#[serde(rename = "enumName")]
pub enum_name: String,
pub values: BTreeMap<String, EnumEntry>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(rename = "EnumEntry")]
pub struct EnumEntry {
pub value: i64,
pub deprecated: bool,
pub description: String,
}

101
xtask/src/generate.rs Normal file
View File

@@ -0,0 +1,101 @@
use color_eyre::eyre;
use std::{collections::BTreeMap, process::Command};
use crate::{enums::Enums, stationpedia::Stationpedia};
mod database;
mod enums;
mod instructions;
mod utils;
pub fn generate(
stationpedia_path: &std::path::Path,
workspace: &std::path::Path,
) -> color_eyre::Result<()> {
let mut pedia: Stationpedia = parse_json(&mut serde_json::Deserializer::from_reader(
std::io::BufReader::new(std::fs::File::open(
stationpedia_path.join("Stationpedia.json"),
)?),
))?;
let instruction_help_patches: BTreeMap<String, String> = parse_json(
&mut serde_json::Deserializer::from_reader(std::io::BufReader::new(std::fs::File::open(
workspace.join("data").join("instruction_help_patches.json"),
)?)),
)?;
for (inst, patch) in instruction_help_patches {
if let Some(cmd) = pedia.script_commands.get_mut(&inst) {
cmd.desc = patch;
} else {
eprintln!("Warning: can find instruction '{inst}' to patch help!");
}
}
let enums: Enums = parse_json(&mut serde_json::Deserializer::from_reader(
std::io::BufReader::new(std::fs::File::open(stationpedia_path.join("Enums.json"))?),
))?;
database::generate_database(&pedia, &enums, workspace)?;
let enums_files = enums::generate_enums(&pedia, &enums, workspace)?;
let inst_files = instructions::generate_instructions(&pedia, workspace)?;
let generated_files = [enums_files.as_slice(), inst_files.as_slice()].concat();
eprintln!("Formatting generated files...");
for file in &generated_files {
prepend_genereated_comment(file)?;
}
let mut cmd = Command::new("cargo");
cmd.current_dir(workspace);
cmd.arg("fmt").arg("--");
cmd.args(&generated_files);
cmd.status()?;
Ok(())
}
pub fn parse_json<'a, T: serde::Deserialize<'a>>(
jd: impl serde::Deserializer<'a>,
) -> Result<T, color_eyre::Report> {
let mut track = serde_path_to_error::Track::new();
let path = serde_path_to_error::Deserializer::new(jd, &mut track);
let mut fun = |path: serde_ignored::Path| {
tracing::warn!(key=%path,"Found ignored key");
};
serde_ignored::deserialize(path, &mut fun).map_err(|e| {
eyre::eyre!(
"path: {track} | error = {e}",
track = track.path().to_string(),
)
})
}
fn prepend_genereated_comment(file_path: &std::path::Path) -> color_eyre::Result<()> {
use std::io::Write;
let tmp_path = file_path.with_extension("rs.tmp");
{
let mut tmp = std::fs::File::create(&tmp_path)?;
let mut src = std::fs::File::open(file_path)?;
write!(
&mut tmp,
"// ================================================= \n\
// !! <-----> DO NOT MODIFY <-----> !! \n\
// \n\
// This module was automatically generated by an
// xtask \n\
// \n\
// run `cargo xtask generate` from the workspace \n\
// to regenerate \n\
// \n\
// ================================================= \n\
\n\
\n\
"
)?;
std::io::copy(&mut src, &mut tmp)?;
}
std::fs::remove_file(file_path)?;
std::fs::rename(&tmp_path, file_path)?;
Ok(())
}

View File

@@ -0,0 +1,674 @@
use std::{collections::BTreeMap, io::Write};
use serde_derive::{Deserialize, Serialize};
use crate::{
enums,
stationpedia::{self, Page, Stationpedia},
};
pub fn generate_database(
stationpedia: &stationpedia::Stationpedia,
enums: &enums::Enums,
workspace: &std::path::Path,
) -> color_eyre::Result<()> {
let templates = generate_templates(stationpedia)?;
println!("Writing prefab database ...");
let prefabs: BTreeMap<String, ObjectTemplate> = templates
.into_iter()
.map(|obj| (obj.prefab().prefab_name.clone(), obj))
.collect();
let prefabs_by_hash: BTreeMap<i32, String> = prefabs
.iter()
.map(|(key, val)| (val.prefab().prefab_hash, key.clone()))
.collect();
let structures = prefabs
.iter()
.filter_map(|(_, val)| {
use ObjectTemplate::*;
match val {
Structure(_)
| StructureSlots(_)
| StructureLogic(_)
| StructureLogicDevice(_)
| StructureLogicDeviceMemory(_) => Some(val.prefab().prefab_name.clone()),
Item(_) | ItemSlots(_) | ItemLogic(_) | ItemLogicMemory(_) => None,
}
})
.collect();
let items = prefabs
.iter()
.filter_map(|(_, val)| {
use ObjectTemplate::*;
match val {
Structure(_)
| StructureSlots(_)
| StructureLogic(_)
| StructureLogicDevice(_)
| StructureLogicDeviceMemory(_) => None,
Item(_) | ItemSlots(_) | ItemLogic(_) | ItemLogicMemory(_) => {
Some(val.prefab().prefab_name.clone())
}
}
})
.collect();
let logicable_items = prefabs
.iter()
.filter_map(|(_, val)| {
use ObjectTemplate::*;
match val {
Structure(_)
| StructureSlots(_)
| StructureLogic(_)
| StructureLogicDevice(_)
| StructureLogicDeviceMemory(_)
| Item(_)
| ItemSlots(_) => None,
ItemLogic(_) | ItemLogicMemory(_) => Some(val.prefab().prefab_name.clone()),
}
})
.collect();
let devices = prefabs
.iter()
.filter_map(|(_, val)| {
use ObjectTemplate::*;
match val {
Structure(_) | StructureSlots(_) | StructureLogic(_) | Item(_) | ItemSlots(_)
| ItemLogic(_) | ItemLogicMemory(_) => None,
StructureLogicDevice(_) | StructureLogicDeviceMemory(_) => {
Some(val.prefab().prefab_name.clone())
}
}
})
.collect();
let db: ObjectDatabase = ObjectDatabase {
prefabs,
reagents: stationpedia.reagents.clone(),
enums: enums.clone(),
prefabs_by_hash,
structures,
devices,
items,
logicable_items,
};
let data_path = workspace.join("data");
if !data_path.exists() {
std::fs::create_dir(&data_path)?;
}
let database_path = data_path.join("database.json");
let mut database_file = std::io::BufWriter::new(std::fs::File::create(database_path)?);
serde_json::to_writer(&mut database_file, &db)?;
database_file.flush()?;
Ok(())
}
fn generate_templates(pedia: &Stationpedia) -> color_eyre::Result<Vec<ObjectTemplate>> {
println!("Generating templates ...");
let mut templates: Vec<ObjectTemplate> = Vec::new();
for page in pedia.pages.iter() {
let prefab = PrefabInfo {
prefab_name: page.prefab_name.clone(),
prefab_hash: page.prefab_hash,
desc: page.description.clone(),
name: page.title.clone(),
};
// every page should either by a item or a structure
// in theory every device is logicable
// in theory everything with memory is logicable
match page {
Page {
item: Some(item),
structure: None,
logic_info: None,
slot_inserts,
memory: None,
device: None,
transmission_receiver: None,
wireless_logic: None,
circuit_holder: None,
..
} if slot_inserts.is_empty() => {
templates.push(ObjectTemplate::Item(ItemTemplate {
prefab,
item: item.into(),
}));
}
Page {
item: Some(item),
structure: None,
logic_info: None,
slot_inserts,
memory: None,
device: None,
transmission_receiver: None,
wireless_logic: None,
circuit_holder: None,
..
} => {
templates.push(ObjectTemplate::ItemSlots(ItemSlotsTemplate {
prefab,
item: item.into(),
slots: slot_inserts_to_info(slot_inserts),
}));
}
Page {
item: Some(item),
structure: None,
logic_info: Some(logic),
slot_inserts,
memory: None,
device: None,
transmission_receiver,
wireless_logic,
circuit_holder,
..
} => {
let mut logic: LogicInfo = logic.into();
if !page.mode_insert.is_empty() {
logic.modes = Some(mode_inserts_to_info(&page.mode_insert));
}
logic.transmission_receiver = transmission_receiver.unwrap_or(false);
logic.wireless_logic = wireless_logic.unwrap_or(false);
logic.circuit_holder = circuit_holder.unwrap_or(false);
templates.push(ObjectTemplate::ItemLogic(ItemLogicTemplate {
prefab,
item: item.into(),
logic,
slots: slot_inserts_to_info(slot_inserts),
}));
}
Page {
item: Some(item),
structure: None,
logic_info: Some(logic),
slot_inserts,
memory: Some(memory),
device: None,
transmission_receiver,
wireless_logic,
circuit_holder,
..
} => {
let mut logic: LogicInfo = logic.into();
if !page.mode_insert.is_empty() {
logic.modes = Some(mode_inserts_to_info(&page.mode_insert));
}
logic.transmission_receiver = transmission_receiver.unwrap_or(false);
logic.wireless_logic = wireless_logic.unwrap_or(false);
logic.circuit_holder = circuit_holder.unwrap_or(false);
templates.push(ObjectTemplate::ItemLogicMemory(ItemLogicMemoryTemplate {
prefab,
item: item.into(),
logic,
slots: slot_inserts_to_info(slot_inserts),
memory: memory.into(),
}));
}
Page {
item: None,
structure: Some(structure),
slot_inserts,
logic_info: None,
memory: None,
device: None,
transmission_receiver: None,
wireless_logic: None,
circuit_holder: None,
..
} if slot_inserts.is_empty() => {
templates.push(ObjectTemplate::Structure(StructureTemplate {
prefab,
structure: structure.into(),
}));
// println!("Structure")
}
Page {
item: None,
structure: Some(structure),
slot_inserts,
logic_info: None,
memory: None,
device: None,
transmission_receiver: None,
wireless_logic: None,
circuit_holder: None,
..
} => {
templates.push(ObjectTemplate::StructureSlots(StructureSlotsTemplate {
prefab,
structure: structure.into(),
slots: slot_inserts_to_info(slot_inserts),
}));
// println!("Structure")
}
Page {
item: None,
structure: Some(structure),
logic_info: Some(logic),
slot_inserts,
memory: None,
device: None,
transmission_receiver,
wireless_logic,
circuit_holder,
..
} => {
let mut logic: LogicInfo = logic.into();
if !page.mode_insert.is_empty() {
logic.modes = Some(mode_inserts_to_info(&page.mode_insert));
}
logic.transmission_receiver = transmission_receiver.unwrap_or(false);
logic.wireless_logic = wireless_logic.unwrap_or(false);
logic.circuit_holder = circuit_holder.unwrap_or(false);
templates.push(ObjectTemplate::StructureLogic(StructureLogicTemplate {
prefab,
structure: structure.into(),
logic,
slots: slot_inserts_to_info(slot_inserts),
}));
// println!("Structure")
}
Page {
item: None,
structure: Some(structure),
logic_info: Some(logic),
slot_inserts,
memory: None,
device: Some(device),
transmission_receiver,
wireless_logic,
circuit_holder,
..
} => {
let mut logic: LogicInfo = logic.into();
if !page.mode_insert.is_empty() {
logic.modes = Some(mode_inserts_to_info(&page.mode_insert));
}
logic.transmission_receiver = transmission_receiver.unwrap_or(false);
logic.wireless_logic = wireless_logic.unwrap_or(false);
logic.circuit_holder = circuit_holder.unwrap_or(false);
templates.push(ObjectTemplate::StructureLogicDevice(
StructureLogicDeviceTemplate {
prefab,
structure: structure.into(),
logic,
slots: slot_inserts_to_info(slot_inserts),
device: device.into(),
},
));
// println!("Structure")
}
Page {
item: None,
structure: Some(structure),
logic_info: Some(logic),
slot_inserts,
memory: Some(memory),
device: Some(device),
transmission_receiver,
wireless_logic,
circuit_holder,
..
} => {
let mut logic: LogicInfo = logic.into();
if !page.mode_insert.is_empty() {
logic.modes = Some(mode_inserts_to_info(&page.mode_insert));
}
logic.transmission_receiver = transmission_receiver.unwrap_or(false);
logic.wireless_logic = wireless_logic.unwrap_or(false);
logic.circuit_holder = circuit_holder.unwrap_or(false);
templates.push(ObjectTemplate::StructureLogicDeviceMemory(
StructureLogicDeviceMemoryTemplate {
prefab,
structure: structure.into(),
logic,
slots: slot_inserts_to_info(slot_inserts),
device: device.into(),
memory: memory.into(),
},
));
// println!("Structure")
}
_ => panic!(
"Non conforming: {:?} \n\titem: {:?}\n\tstructure: {:?}\n\tlogic_info: {:?}\n\tslot_inserts: {:?}\n\tslot_logic: {:?}\n\tmemory: {:?}\n\tdevice: {:?}",
page.key,
page.item,
page.structure,
page.logic_info,
page.slot_inserts,
page.logic_slot_insert,
page.memory,
page.device,
),
}
}
Ok(templates)
}
fn slot_inserts_to_info(slots: &[stationpedia::SlotInsert]) -> Vec<SlotInfo> {
let mut tmp: Vec<_> = slots.into();
tmp.sort_by(|a, b| a.slot_index.cmp(&b.slot_index));
tmp.iter()
.map(|slot| SlotInfo {
name: slot.slot_name.clone(),
typ: slot.slot_type.clone(),
})
.collect()
}
fn mode_inserts_to_info(modes: &[stationpedia::ModeInsert]) -> BTreeMap<u32, String> {
modes
.iter()
.map(|mode| (mode.logic_access_types, mode.logic_name.clone()))
.collect()
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct ObjectDatabase {
pub prefabs: BTreeMap<String, ObjectTemplate>,
pub reagents: BTreeMap<String, stationpedia::Reagent>,
pub enums: enums::Enums,
pub prefabs_by_hash: BTreeMap<i32, String>,
pub structures: Vec<String>,
pub devices: Vec<String>,
pub items: Vec<String>,
pub logicable_items: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ObjectTemplate {
Structure(StructureTemplate),
StructureSlots(StructureSlotsTemplate),
StructureLogic(StructureLogicTemplate),
StructureLogicDevice(StructureLogicDeviceTemplate),
StructureLogicDeviceMemory(StructureLogicDeviceMemoryTemplate),
Item(ItemTemplate),
ItemSlots(ItemSlotsTemplate),
ItemLogic(ItemLogicTemplate),
ItemLogicMemory(ItemLogicMemoryTemplate),
}
impl ObjectTemplate {
fn prefab(&self) -> &PrefabInfo {
use ObjectTemplate::*;
match self {
Structure(s) => &s.prefab,
StructureSlots(s) => &s.prefab,
StructureLogic(s) => &s.prefab,
StructureLogicDevice(s) => &s.prefab,
StructureLogicDeviceMemory(s) => &s.prefab,
Item(i) => &i.prefab,
ItemSlots(i) => &i.prefab,
ItemLogic(i) => &i.prefab,
ItemLogicMemory(i) => &i.prefab,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct PrefabInfo {
pub prefab_name: String,
pub prefab_hash: i32,
pub desc: String,
pub name: String,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct SlotInfo {
pub name: String,
pub typ: String,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct LogicInfo {
pub logic_slot_types: BTreeMap<u32, stationpedia::LogicSlotTypes>,
pub logic_types: stationpedia::LogicTypes,
#[serde(skip_serializing_if = "Option::is_none")]
pub modes: Option<BTreeMap<u32, String>>,
pub transmission_receiver: bool,
pub wireless_logic: bool,
pub circuit_holder: bool,
}
impl From<&stationpedia::LogicInfo> for LogicInfo {
fn from(value: &stationpedia::LogicInfo) -> Self {
LogicInfo {
logic_slot_types: value.logic_slot_types.clone(),
logic_types: value.logic_types.clone(),
modes: None,
transmission_receiver: false,
wireless_logic: false,
circuit_holder: false,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct ItemInfo {
pub consumable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter_type: Option<String>,
pub ingredient: bool,
pub max_quantity: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub reagents: Option<BTreeMap<String, f64>>,
pub slot_class: String,
pub sorting_class: String,
}
impl From<&stationpedia::Item> for ItemInfo {
fn from(item: &stationpedia::Item) -> Self {
ItemInfo {
consumable: item.consumable.unwrap_or(false),
filter_type: item.filter_type.clone(),
ingredient: item.ingredient.unwrap_or(false),
max_quantity: item.max_quantity.unwrap_or(1.0),
reagents: item
.reagents
.as_ref()
.map(|map| map.iter().map(|(key, val)| (key.clone(), *val)).collect()),
slot_class: item.slot_class.clone(),
sorting_class: item.sorting_class.clone(),
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct DeviceInfo {
pub connection_list: Vec<(String, String)>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_pins_length: Option<i64>,
pub has_activate_state: bool,
pub has_atmosphere: bool,
pub has_color_state: bool,
pub has_lock_state: bool,
pub has_mode_state: bool,
pub has_on_off_state: bool,
pub has_open_state: bool,
pub has_reagents: bool,
}
impl From<&stationpedia::Device> for DeviceInfo {
fn from(value: &stationpedia::Device) -> Self {
DeviceInfo {
connection_list: value.connection_list.clone(),
device_pins_length: value.devices_length,
has_activate_state: value.has_activate_state,
has_atmosphere: value.has_atmosphere,
has_color_state: value.has_color_state,
has_lock_state: value.has_lock_state,
has_mode_state: value.has_mode_state,
has_on_off_state: value.has_on_off_state,
has_open_state: value.has_open_state,
has_reagents: value.has_reagents,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct StructureInfo {
pub small_grid: bool,
}
impl From<&stationpedia::Structure> for StructureInfo {
fn from(value: &stationpedia::Structure) -> Self {
StructureInfo {
small_grid: value.small_grid,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct Instruction {
pub description: String,
pub typ: String,
pub value: i64,
}
impl From<&stationpedia::Instruction> for Instruction {
fn from(value: &stationpedia::Instruction) -> Self {
Instruction {
description: value.description.clone(),
typ: value.type_.clone(),
value: value.value,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct MemoryInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<BTreeMap<String, Instruction>>,
pub memory_access: String,
pub memory_size: i64,
}
impl From<&stationpedia::Memory> for MemoryInfo {
fn from(value: &stationpedia::Memory) -> Self {
MemoryInfo {
instructions: value.instructions.as_ref().map(|insts| {
insts
.iter()
.map(|(key, value)| (key.clone(), value.into()))
.collect()
}),
memory_access: value.memory_access.clone(),
memory_size: value.memory_size,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct StructureTemplate {
pub prefab: PrefabInfo,
pub structure: StructureInfo,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct StructureSlotsTemplate {
pub prefab: PrefabInfo,
pub structure: StructureInfo,
pub slots: Vec<SlotInfo>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct StructureLogicTemplate {
pub prefab: PrefabInfo,
pub structure: StructureInfo,
pub logic: LogicInfo,
pub slots: Vec<SlotInfo>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct StructureLogicDeviceTemplate {
pub prefab: PrefabInfo,
pub structure: StructureInfo,
pub logic: LogicInfo,
pub slots: Vec<SlotInfo>,
pub device: DeviceInfo,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct StructureLogicDeviceMemoryTemplate {
pub prefab: PrefabInfo,
pub structure: StructureInfo,
pub logic: LogicInfo,
pub slots: Vec<SlotInfo>,
pub device: DeviceInfo,
pub memory: MemoryInfo,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct ItemTemplate {
pub prefab: PrefabInfo,
pub item: ItemInfo,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct ItemSlotsTemplate {
pub prefab: PrefabInfo,
pub item: ItemInfo,
pub slots: Vec<SlotInfo>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct ItemLogicTemplate {
pub prefab: PrefabInfo,
pub item: ItemInfo,
pub logic: LogicInfo,
pub slots: Vec<SlotInfo>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct ItemLogicMemoryTemplate {
pub prefab: PrefabInfo,
pub item: ItemInfo,
pub logic: LogicInfo,
pub slots: Vec<SlotInfo>,
pub memory: MemoryInfo,
}

397
xtask/src/generate/enums.rs Normal file
View File

@@ -0,0 +1,397 @@
use convert_case::{Case, Casing};
use std::collections::BTreeMap;
use std::{
fmt::Display,
io::{BufWriter, Write},
path::PathBuf,
str::FromStr,
};
pub fn generate_enums(
stationpedia: &crate::stationpedia::Stationpedia,
enums: &crate::enums::Enums,
workspace: &std::path::Path,
) -> color_eyre::Result<Vec<PathBuf>> {
println!("Writing Enum Listings ...");
let enums_path = workspace
.join("ic10emu")
.join("src")
.join("vm")
.join("enums");
if !enums_path.exists() {
std::fs::create_dir(&enums_path)?;
}
let mut writer =
std::io::BufWriter::new(std::fs::File::create(enums_path.join("script_enums.rs"))?);
write_repr_enum_use_header(&mut writer)?;
for enm in enums.script_enums.values() {
write_enum_listing(&mut writer, enm)?;
}
let mut writer =
std::io::BufWriter::new(std::fs::File::create(enums_path.join("basic_enums.rs"))?);
write_repr_enum_use_header(&mut writer)?;
for enm in enums.basic_enums.values() {
write_enum_listing(&mut writer, enm)?;
}
write_enum_aggragate_mod(&mut writer, &enums.basic_enums)?;
let mut writer = std::io::BufWriter::new(std::fs::File::create(enums_path.join("prefabs.rs"))?);
write_repr_enum_use_header(&mut writer)?;
let prefabs = stationpedia
.pages
.iter()
.map(|page| {
let variant = ReprEnumVariant {
value: page.prefab_hash,
deprecated: false,
props: vec![
("name".to_owned(), page.title.clone()),
("desc".to_owned(), page.description.clone()),
],
};
(page.prefab_name.clone(), variant)
})
.collect::<Vec<_>>();
write_repr_enum(&mut writer, "StationpediaPrefab", &prefabs, true)?;
Ok(vec![
enums_path.join("script_enums.rs"),
enums_path.join("basic_enums.rs"),
enums_path.join("prefabs.rs"),
])
}
fn write_enum_aggragate_mod<T: std::io::Write>(
writer: &mut BufWriter<T>,
enums: &BTreeMap<String, crate::enums::EnumListing>,
) -> color_eyre::Result<()> {
let variant_lines = enums
.iter()
.map(|(name, listing)| {
let name = if name.is_empty() || name == "_unnamed" {
"Unnamed"
} else {
name
};
format!(
" {}({}),",
name.to_case(Case::Pascal),
listing.enum_name.to_case(Case::Pascal)
)
})
.collect::<Vec<_>>()
.join("\n");
let value_arms = enums
.keys()
.map(|name| {
let variant_name = (if name.is_empty() || name == "_unnamed" {
"Unnamed"
} else {
name
})
.to_case(Case::Pascal);
format!(" Self::{variant_name}(enm) => *enm as u32,",)
})
.collect::<Vec<_>>()
.join("\n");
let get_str_arms = enums
.keys()
.map(|name| {
let variant_name = (if name.is_empty() || name == "_unnamed" {
"Unnamed"
} else {
name
})
.to_case(Case::Pascal);
format!(" Self::{variant_name}(enm) => enm.get_str(prop),",)
})
.collect::<Vec<_>>()
.join("\n");
let iter_chain = enums
.iter()
.enumerate()
.map(|(index, (name, listing))| {
let variant_name = (if name.is_empty() || name == "_unnamed" {
"Unnamed"
} else {
name
})
.to_case(Case::Pascal);
let enum_name = listing.enum_name.to_case(Case::Pascal);
if index == 0 {
format!("{enum_name}::iter().map(|enm| Self::{variant_name}(enm))")
} else {
format!(".chain({enum_name}::iter().map(|enm| Self::{variant_name}(enm)))")
}
})
.collect::<Vec<_>>()
.join("\n");
write!(
writer,
"pub enum BasicEnum {{\n\
{variant_lines}
}}\n\
impl BasicEnum {{\n \
pub fn get_value(&self) -> u32 {{\n \
match self {{\n \
{value_arms}\n \
}}\n \
}}\n\
pub fn get_str(&self, prop: &str) -> Option<&'static str> {{\n \
match self {{\n \
{get_str_arms}\n \
}}\n \
}}\n\
pub fn iter() -> impl std::iter::Iterator<Item = Self> {{\n \
use strum::IntoEnumIterator;\n \
{iter_chain}\n \
}}
}}\n\
"
)?;
let arms = enums
.iter()
.flat_map(|(name, listing)| {
let variant_name = (if name.is_empty() || name == "_unnamed" {
"Unnamed"
} else {
name
})
.to_case(Case::Pascal);
let name = if name == "_unnamed" {
"".to_string()
} else {
name.clone()
};
let enum_name = listing.enum_name.to_case(Case::Pascal);
listing.values.keys().map(move |variant| {
let sep = if name.is_empty() { "" } else { "." };
let pat = format!("{name}{sep}{variant}").to_lowercase();
let variant = variant.to_case(Case::Pascal);
format!("\"{pat}\" => Ok(Self::{variant_name}({enum_name}::{variant})),")
})
})
.collect::<Vec<_>>()
.join("\n ");
write!(
writer,
"\
impl std::str::FromStr for BasicEnum {{\n \
type Err = crate::errors::ParseError;\n \
fn from_str(s: &str) -> Result<Self, Self::Err> {{\n \
let end = s.len();\n \
match s {{\n \
{arms}\n \
_ => Err(crate::errors::ParseError{{ line: 0, start: 0, end, msg: format!(\"Unknown enum '{{}}'\", s) }})\n \
}}\n \
}}\n\
}}\
"
)?;
let display_arms = enums
.keys()
.map(|name| {
let variant_name = (if name.is_empty() || name == "_unnamed" {
"Unnamed"
} else {
name
})
.to_case(Case::Pascal);
let name = if name == "_unnamed" {
"".to_string()
} else {
name.clone()
};
let sep = if name.is_empty() || name == "_unnamed" {
""
} else {
"."
};
let pat = format!("{name}{sep}{{}}");
format!(" Self::{variant_name}(enm) => write!(f, \"{pat}\", enm),",)
})
.collect::<Vec<_>>()
.join("\n ");
write!(
writer,
"\
impl std::fmt::Display for BasicEnum {{\n \
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{\n \
match self {{\n \
{display_arms}\n \
}}\n \
}}\n\
}}\
"
)?;
Ok(())
}
pub fn write_enum_listing<T: std::io::Write>(
writer: &mut BufWriter<T>,
enm: &crate::enums::EnumListing,
) -> color_eyre::Result<()> {
let max = enm
.values
.values()
.map(|var| var.value)
.max()
.expect("enum should have max value");
let min = enm
.values
.values()
.map(|var| var.value)
.min()
.expect("enum should have min value");
if max < u8::MAX as i64 && min >= u8::MIN as i64 {
let variants: Vec<_> = enm
.values
.iter()
.map(|(n, var)| {
let variant = ReprEnumVariant {
value: var.value as u8,
deprecated: var.deprecated,
props: vec![("docs".to_owned(), var.description.to_owned())],
};
(n.clone(), variant)
})
.collect();
write_repr_enum(
writer,
&enm.enum_name.to_case(Case::Pascal),
&variants,
true,
)?;
} else if max < u16::MAX as i64 && min >= u16::MIN as i64 {
let variants: Vec<_> = enm
.values
.iter()
.map(|(n, var)| {
let variant = ReprEnumVariant {
value: var.value as u16,
deprecated: var.deprecated,
props: vec![("docs".to_owned(), var.description.to_owned())],
};
(n.clone(), variant)
})
.collect();
write_repr_enum(writer, &enm.enum_name, &variants, true)?;
} else if max < u32::MAX as i64 && min >= u32::MIN as i64 {
let variants: Vec<_> = enm
.values
.iter()
.map(|(n, var)| {
let variant = ReprEnumVariant {
value: var.value as u32,
deprecated: var.deprecated,
props: vec![("docs".to_owned(), var.description.to_owned())],
};
(n.clone(), variant)
})
.collect();
write_repr_enum(writer, &enm.enum_name, &variants, true)?;
} else if max < i32::MAX as i64 && min >= i32::MIN as i64 {
let variants: Vec<_> = enm
.values
.iter()
.map(|(n, var)| {
let variant = ReprEnumVariant {
value: var.value as i32,
deprecated: var.deprecated,
props: vec![("docs".to_owned(), var.description.to_owned())],
};
(n.clone(), variant)
})
.collect();
write_repr_enum(writer, &enm.enum_name, &variants, true)?;
} else {
let variants: Vec<_> = enm
.values
.iter()
.map(|(n, var)| {
let variant = ReprEnumVariant {
value: var.value as i32,
deprecated: var.deprecated,
props: vec![("docs".to_owned(), var.description.to_owned())],
};
(n.clone(), variant)
})
.collect();
write_repr_enum(writer, &enm.enum_name, &variants, true)?;
}
Ok(())
}
struct ReprEnumVariant<P>
where
P: Display + FromStr,
{
pub value: P,
pub deprecated: bool,
pub props: Vec<(String, String)>,
}
fn write_repr_enum_use_header<T: std::io::Write>(
writer: &mut BufWriter<T>,
) -> color_eyre::Result<()> {
write!(
writer,
"use serde::{{Deserialize, Serialize}};\n\
use strum::{{\n \
AsRefStr, Display, EnumIter, EnumProperty, EnumString, FromRepr,\n\
}};\n"
)?;
Ok(())
}
fn write_repr_enum<'a, T: std::io::Write, I, P>(
writer: &mut BufWriter<T>,
name: &str,
variants: I,
use_phf: bool,
) -> color_eyre::Result<()>
where
P: Display + FromStr + 'a,
I: IntoIterator<Item = &'a (String, ReprEnumVariant<P>)>,
{
let additional_strum = if use_phf { "#[strum(use_phf)]\n" } else { "" };
let repr = std::any::type_name::<P>();
write!(
writer,
"#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumString, AsRefStr, EnumProperty, EnumIter, FromRepr, Serialize, Deserialize)]\n\
{additional_strum}\
#[repr({repr})]\n\
pub enum {name} {{\n"
)?;
for (name, variant) in variants {
let variant_name = name.replace('.', "").to_case(Case::Pascal);
let serialize = vec![name.clone()];
let serialize_str = serialize
.into_iter()
.map(|s| format!("serialize = \"{s}\""))
.collect::<Vec<String>>()
.join(", ");
let mut props = Vec::new();
if variant.deprecated {
props.push("deprecated = \"true\"".to_owned());
}
for (prop_name, prop_val) in &variant.props {
props.push(format!("{prop_name} = r#\"{prop_val}\"#"));
}
let val = &variant.value;
props.push(format!("value = \"{val}\""));
let props_str = if !props.is_empty() {
format!(", props( {} )", props.join(", "))
} else {
"".to_owned()
};
writeln!(
writer,
" #[strum({serialize_str}{props_str})] {variant_name} = {val}{repr},"
)?;
}
writeln!(writer, "}}")?;
Ok(())
}

View File

@@ -0,0 +1,216 @@
use convert_case::{Case, Casing};
use std::{collections::BTreeMap, path::PathBuf};
use crate::{generate::utils, stationpedia};
pub fn generate_instructions(
stationpedia: &stationpedia::Stationpedia,
workspace: &std::path::Path,
) -> color_eyre::Result<Vec<PathBuf>> {
let instructions_path = workspace
.join("ic10emu")
.join("src")
.join("vm")
.join("instructions");
if !instructions_path.exists() {
std::fs::create_dir(&instructions_path)?;
}
let mut writer =
std::io::BufWriter::new(std::fs::File::create(instructions_path.join("enums.rs"))?);
write_instructions_enum(&mut writer, &stationpedia.script_commands)?;
let mut writer =
std::io::BufWriter::new(std::fs::File::create(instructions_path.join("traits.rs"))?);
write_instruction_interface_trait(&mut writer)?;
for (typ, info) in &stationpedia.script_commands {
write_instruction_trait(&mut writer, (typ, info))?;
}
write_instruction_super_trait(&mut writer, &stationpedia.script_commands)?;
Ok(vec![
instructions_path.join("enums.rs"),
instructions_path.join("traits.rs"),
])
}
fn write_instructions_enum<T: std::io::Write>(
writer: &mut T,
instructions: &BTreeMap<String, stationpedia::Command>,
) -> color_eyre::Result<()> {
println!("Writing instruction Listings ...");
let mut instructions = instructions.clone();
for (_, ref mut info) in instructions.iter_mut() {
info.example = utils::strip_color(&info.example);
}
write!(
writer,
"use serde::{{Deserialize, Serialize}};\n\
use strum::{{\n \
Display, EnumIter, EnumProperty, EnumString, FromRepr,\n\
}};\n
use crate::vm::object::traits::Programmable;\n\
use crate::vm::instructions::traits::*;\n\
"
)?;
write!(
writer,
"#[derive(Debug, Display, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)]\n\
#[derive(EnumIter, EnumString, EnumProperty, FromRepr)]\n\
#[strum(use_phf, serialize_all = \"lowercase\")]\n\
#[serde(rename_all = \"lowercase\")]\n\
pub enum InstructionOp {{\n\
"
)?;
writeln!(writer, " Nop,")?;
for (name, info) in &instructions {
let props_str = format!(
"props( example = \"{}\", desc = \"{}\", operands = \"{}\" )",
&info.example,
&info.desc,
count_operands(&info.example)
);
writeln!(
writer,
" #[strum({props_str})] {},",
name.to_case(Case::Pascal)
)?;
}
writeln!(writer, "}}")?;
write!(
writer,
"impl InstructionOp {{\n \
pub fn num_operands(&self) -> usize {{\n \
self.get_str(\"operands\").expect(\"instruction without operand property\").parse::<usize>().expect(\"invalid instruction operand property\")\n \
}}\n\
\n \
pub fn execute<T>(\n \
&self,\n \
ic: &mut T,\n \
vm: &crate::vm::VM,\n \
operands: &[crate::vm::instructions::operands::Operand],\n \
) -> Result<(), crate::errors::ICError>\n \
where\n \
T: Programmable,\n\
{{\n \
let num_operands = self.num_operands();\n \
if operands.len() != num_operands {{\n \
return Err(crate::errors::ICError::mismatch_operands(operands.len(), num_operands as u32));\n \
}}\n \
match self {{\n \
Self::Nop => Ok(()),\n \
"
)?;
for (name, info) in instructions {
let num_operands = count_operands(&info.example);
let operands = (0..num_operands)
.map(|i| format!("&operands[{}]", i))
.collect::<Vec<_>>()
.join(", ");
let trait_name = name.to_case(Case::Pascal);
writeln!(
writer,
" Self::{trait_name} => ic.execute_{name}(vm, {operands}),",
)?;
}
write!(
writer,
" }}\
}}\n\
}}
"
)?;
Ok(())
}
fn write_instruction_trait<T: std::io::Write>(
writer: &mut T,
instruction: (&str, &stationpedia::Command),
) -> color_eyre::Result<()> {
let (name, info) = instruction;
let trait_name = format!("{}Instruction", name.to_case(Case::Pascal));
let operands = operand_names(&info.example)
.iter()
.map(|name| {
format!(
"{}: &crate::vm::instructions::operands::Operand",
name.to_case(Case::Snake)
)
})
.collect::<Vec<_>>()
.join(", ");
let example = utils::strip_color(&info.example);
write!(
writer,
"pub trait {trait_name}: IntegratedCircuit {{\n \
/// {example} \n \
fn execute_{name}(&mut self, vm: &crate::vm::VM, {operands}) -> Result<(), crate::errors::ICError>;\n\
}}"
)?;
Ok(())
}
fn count_operands(example: &str) -> usize {
example.split(' ').count() - 1
}
fn operand_names(example: &str) -> Vec<String> {
utils::strip_color(example)
.split(' ')
.skip(1)
.map(|name| name.split(['?', '(']).next().unwrap().to_string())
.collect()
}
fn write_instruction_interface_trait<T: std::io::Write>(writer: &mut T) -> color_eyre::Result<()> {
write!(
writer,
"\
use std::collections::BTreeMap;\n\
use crate::vm::object::traits::{{Logicable, MemoryWritable, SourceCode}};\n\
use crate::errors::ICError; \n\
pub trait IntegratedCircuit: Logicable + MemoryWritable + SourceCode {{\n \
fn get_instruciton_pointer(&self) -> usize;\n \
fn set_next_instruction(&mut self, next_instruction: usize);\n \
fn reset(&mut self);\n \
fn get_real_target(&self, indirection: u32, target: u32) -> Result<f64, ICError>;\n \
fn get_register(&self, indirection: u32, target: u32) -> Result<f64, ICError>;\n \
fn set_register(&mut self, indirection: u32, target: u32, val: f64) -> Result<f64, ICError>;\n \
fn set_return_address(&mut self, addr: f64);\n \
fn push_stack(&mut self, val: f64) -> Result<f64, ICError>;\n \
fn pop_stack(&mut self) -> Result<f64, ICError>;\n \
fn peek_stack(&self) -> Result<f64, ICError>;\n \
fn get_aliases(&self) -> &BTreeMap<String, crate::vm::instructions::operands::Operand>;\n \
fn get_defines(&self) -> &BTreeMap<String, f64>;\n \
fn get_lables(&self) -> &BTreeMap<String, u32>;\n\
}}\n\
"
)?;
Ok(())
}
fn write_instruction_super_trait<T: std::io::Write>(
writer: &mut T,
instructions: &BTreeMap<String, stationpedia::Command>,
) -> color_eyre::Result<()> {
let traits = instructions
.keys()
.map(|name| format!("{}Instruction", name.to_case(Case::Pascal)))
.collect::<Vec<_>>()
.join(" + ");
write!(
writer,
"\
pub trait ICInstructable: {traits} {{}}\n\
impl <T> ICInstructable for T where T: {traits} {{}}
"
)?;
Ok(())
}

View File

@@ -0,0 +1,60 @@
use onig::{Captures, Regex, RegexOptions, Syntax};
pub fn strip_color(s: &str) -> String {
let color_regex = Regex::with_options(
r#"<color=(#?\w+)>((:?(?!<color=(?:#?\w+)>).)+?)</color>"#,
RegexOptions::REGEX_OPTION_MULTILINE | RegexOptions::REGEX_OPTION_CAPTURE_GROUP,
Syntax::default(),
)
.unwrap();
let mut new = s.to_owned();
loop {
new = color_regex.replace_all(&new, |caps: &Captures| caps.at(2).unwrap_or("").to_string());
if !color_regex.is_match(&new) {
break;
}
}
new
}
#[allow(dead_code)]
pub fn color_to_heml(s: &str) -> String {
let color_regex = Regex::with_options(
r#"<color=(#?\w+)>((:?(?!<color=(?:#?\w+)>).)+?)</color>"#,
RegexOptions::REGEX_OPTION_MULTILINE | RegexOptions::REGEX_OPTION_CAPTURE_GROUP,
Syntax::default(),
)
.unwrap();
let mut new = s.to_owned();
loop {
new = color_regex.replace_all(&new, |caps: &Captures| {
format!(
r#"<div style="color: {};">{}</div>"#,
caps.at(1).unwrap_or(""),
caps.at(2).unwrap_or("")
)
});
if !color_regex.is_match(&new) {
break;
}
}
new
}
#[allow(dead_code)]
pub fn strip_link(s: &str) -> String {
let link_regex = Regex::with_options(
r#"<link=(\w+)>(.+?)</link>"#,
RegexOptions::REGEX_OPTION_MULTILINE | RegexOptions::REGEX_OPTION_CAPTURE_GROUP,
Syntax::default(),
)
.unwrap();
let mut new = s.to_owned();
loop {
new = link_regex.replace_all(&new, |caps: &Captures| caps.at(2).unwrap_or("").to_string());
if !link_regex.is_match(&new) {
break;
}
}
new
}

View File

@@ -2,6 +2,10 @@ use std::process::{Command, ExitStatus};
use clap::{Parser, Subcommand};
mod enums;
mod generate;
mod stationpedia;
/// Helper program to start ic10emu and website.
///
/// Can be invoked as `cargo xtask <command>`
@@ -44,11 +48,19 @@ enum Task {
Deploy {},
/// bump the cargo.toml and package,json versions
Version {
#[arg(last = true, default_value = "patch", value_parser = clap::builder::PossibleValuesParser::new(VALID_VERSION_TYPE))]
#[arg(default_value = "patch", value_parser = clap::builder::PossibleValuesParser::new(VALID_VERSION_TYPE))]
version: String,
},
/// update changelog
Changelog {},
Generate {
#[arg()]
/// Path to Stationeers installation. Used to locate "Stationpedia.json" and "Enums.json"
/// generated by https://github.com/Ryex/StationeersStationpediaExtractor
/// Otherwise looks for both files in `<workspace>` or `<workspace>/data`.
/// Can also point directly at a folder containing the two files.
path: Option<std::ffi::OsString>,
},
}
#[derive(thiserror::Error)]
@@ -57,6 +69,8 @@ enum Error {
BuildFailed(String, String, std::process::ExitStatus),
#[error("failed to run command `{0}`")]
Command(String, #[source] std::io::Error),
#[error("can not find `Stationpedia.json` and/or `Enums.json` at `{0}`")]
BadStationeresPath(std::path::PathBuf),
}
impl std::fmt::Debug for Error {
@@ -75,7 +89,8 @@ impl std::fmt::Debug for Error {
}
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
fn main() -> Result<(), Error> {
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
let args = Args::parse();
let workspace = {
let out = Command::new("cargo")
@@ -132,21 +147,78 @@ fn main() -> Result<(), Error> {
cmd.args(["version", &version]).status().map_err(|e| {
Error::Command(format!("{}", cmd.get_program().to_string_lossy()), e)
})?;
},
Task::Changelog { } => {
}
Task::Changelog {} => {
let mut cmd = Command::new("git-changelog");
cmd.current_dir(&workspace);
cmd.args([
"-io", "CHANGELOG.md",
"-t", "path:CHANGELOG.md.jinja",
"-c", "conventional",
"--bump", VERSION.unwrap_or("auto"),
"-io",
"CHANGELOG.md",
"-t",
"path:CHANGELOG.md.jinja",
"-c",
"conventional",
"--bump",
VERSION.unwrap_or("auto"),
"--parse-refs",
"--trailers"
]).status().map_err(|e| {
Error::Command(format!("{}", cmd.get_program().to_string_lossy()), e)
})?;
},
"--trailers",
])
.status()
.map_err(|e| Error::Command(format!("{}", cmd.get_program().to_string_lossy()), e))?;
}
Task::Generate { path } => {
let path = match path {
Some(path) => {
let mut path = std::path::PathBuf::from(path);
if path.exists()
&& path
.parent()
.and_then(|p| p.file_name())
.is_some_and(|p| p == "Stationeers")
&& path.file_name().is_some_and(|name| {
(std::env::consts::OS == "windows" && name == "rocketstation.exe")
|| (name == "rocketstation")
|| (name == "rocketstation_Data")
})
{
path = path.parent().unwrap().to_path_buf();
}
if path.is_dir()
&& path.file_name().is_some_and(|name| name == "Stationeers")
&& path.join("Stationpedia").join("Stationpedia.json").exists()
{
path = path.join("Stationpedia");
}
if path.is_file()
&& path
.file_name()
.is_some_and(|name| name == "Stationpedia.json")
{
path = path.parent().unwrap().to_path_buf();
}
path
}
None => {
let mut path = workspace.clone();
if path.join("data").join("Stationpedia.json").exists()
&& path.join("data").join("Enums.json").exists()
{
path = path.join("data")
}
path
}
};
if path.is_dir()
&& path.join("Stationpedia.json").exists()
&& path.join("Enums.json").exists()
{
generate::generate(&path, &workspace)?;
} else {
return Err(Error::BadStationeresPath(path).into());
}
}
}
Ok(())
}

346
xtask/src/stationpedia.rs Normal file
View File

@@ -0,0 +1,346 @@
use serde_derive::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename = "Stationpedia", deny_unknown_fields)]
pub struct Stationpedia {
pub pages: Vec<Page>,
pub reagents: BTreeMap<String, Reagent>,
#[serde(rename = "scriptCommands")]
pub script_commands: BTreeMap<String, Command>,
}
#[allow(dead_code)]
impl Stationpedia {
pub fn lookup_prefab_name(&self, prefab_name: &'_ str) -> Option<&Page> {
self.pages.iter().find(|p| p.prefab_name == prefab_name)
}
pub fn lookup_key(&self, key: &str) -> Option<&Page> {
self.pages.iter().find(|p| p.key == key)
}
pub fn lookup_hash(&self, hash: i32) -> Option<&Page> {
self.pages.iter().find(|p| p.prefab_hash == hash)
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Reagent {
#[serde(rename = "Hash")]
pub hash: i64,
#[serde(rename = "Unit")]
pub unit: String,
#[serde(rename = "Sources")]
pub sources: Option<BTreeMap<String, f64>>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Command {
pub desc: String,
pub example: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Page {
#[serde(rename = "ConnectionInsert")]
pub connection_insert: Vec<ConnectionInsert>,
#[serde(rename = "ConstructedByKits")]
pub constructs: Vec<Constructs>,
#[serde(rename = "Description")]
pub description: String,
#[serde(rename = "Device")]
pub device: Option<Device>,
/// the item , if none then deprecated
#[serde(rename = "Item")]
pub item: Option<Item>,
#[serde(rename = "Structure")]
pub structure: Option<Structure>,
#[serde(rename = "Key")]
pub key: String,
#[serde(rename = "LogicInfo")]
pub logic_info: Option<LogicInfo>,
#[serde(rename = "LogicInsert")]
pub logic_insert: Vec<LogicInsert>,
#[serde(rename = "LogicSlotInsert")]
pub logic_slot_insert: Vec<LogicSlotInsert>,
#[serde(rename = "Memory")]
pub memory: Option<Memory>,
#[serde(rename = "ModeInsert")]
pub mode_insert: Vec<ModeInsert>,
#[serde(rename = "PrefabHash")]
pub prefab_hash: i32,
#[serde(rename = "PrefabName")]
pub prefab_name: String,
#[serde(rename = "SlotInserts")]
pub slot_inserts: Vec<SlotInsert>,
#[serde(rename = "Title")]
pub title: String,
#[serde(rename = "TransmissionReceiver")]
pub transmission_receiver: Option<bool>,
#[serde(rename = "WirelessLogic")]
pub wireless_logic: Option<bool>,
#[serde(rename = "CircuitHolder")]
pub circuit_holder: Option<bool>,
#[serde(rename = "BasePowerDraw")]
pub base_power_draw: Option<String>,
#[serde(rename = "MaxPressure")]
pub max_pressure: Option<String>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Constructs {
#[serde(rename = "NameOfThing")]
pub name_of_thing: String,
#[serde(rename = "PageLink")]
pub page_link: String,
#[serde(rename = "PrefabHash")]
pub prefab_hash: i32,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Structure {
#[serde(rename = "SmallGrid")]
pub small_grid: bool,
#[serde(rename = "BuildStates")]
pub build_states: BuildStates,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct BuildStates(pub Vec<BuildState>);
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BuildState {
#[serde(rename = "Tool")]
pub tool: Option<Vec<Tool>>,
#[serde(rename = "ToolExit")]
pub tool_exit: Option<Vec<Tool>>,
#[serde(rename = "CanManufacture", default)]
pub can_manufacture: bool,
#[serde(rename = "MachineTier")]
pub machine_tier: Option<MachineTier>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum MachineTier {
Undefined,
TierOne,
TierTwo,
TierThree,
Max,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Tool {
#[serde(rename = "IsTool", default)]
pub is_tool: bool,
#[serde(rename = "PrefabName")]
pub prefab_name: String,
#[serde(rename = "Quantity")]
pub quantity: Option<i64>,
}
#[serde_as]
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SlotInsert {
#[serde(rename = "SlotIndex")]
#[serde_as(as = "DisplayFromStr")]
pub slot_index: u32,
#[serde(rename = "SlotName")]
pub slot_name: String,
#[serde(rename = "SlotType")]
pub slot_type: String,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogicInsert {
#[serde(rename = "LogicAccessTypes")]
pub logic_access_types: String,
#[serde(rename = "LogicName")]
pub logic_name: String,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogicSlotInsert {
#[serde(rename = "LogicAccessTypes")]
pub logic_access_types: String,
#[serde(rename = "LogicName")]
pub logic_name: String,
}
#[serde_as]
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ModeInsert {
#[serde(rename = "LogicAccessTypes")]
#[serde_as(as = "DisplayFromStr")]
pub logic_access_types: u32,
#[serde(rename = "LogicName")]
pub logic_name: String,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConnectionInsert {
#[serde(rename = "LogicAccessTypes")]
pub logic_access_types: String,
#[serde(rename = "LogicName")]
pub logic_name: String,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogicInfo {
#[serde(rename = "LogicSlotTypes")]
pub logic_slot_types: BTreeMap<u32, LogicSlotTypes>,
#[serde(rename = "LogicTypes")]
pub logic_types: LogicTypes,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct LogicSlotTypes {
#[serde(flatten)]
pub slot_types: BTreeMap<String, String>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct LogicTypes {
#[serde(flatten)]
pub types: BTreeMap<String, String>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Memory {
#[serde(rename = "Instructions")]
pub instructions: Option<BTreeMap<String, Instruction>>,
#[serde(rename = "MemoryAccess")]
pub memory_access: String,
#[serde(rename = "MemorySize")]
pub memory_size: i64,
#[serde(rename = "MemorySizeReadable")]
pub memory_size_readable: String,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Instruction {
#[serde(rename = "Description")]
pub description: String,
#[serde(rename = "Type")]
pub type_: String,
#[serde(rename = "Value")]
pub value: i64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Item {
#[serde(rename = "Consumable")]
pub consumable: Option<bool>,
#[serde(rename = "FilterType")]
pub filter_type: Option<String>,
#[serde(rename = "Ingredient")]
pub ingredient: Option<bool>,
#[serde(rename = "MaxQuantity")]
pub max_quantity: Option<f64>,
#[serde(rename = "Reagents")]
pub reagents: Option<indexmap::IndexMap<String, f64>>,
#[serde(rename = "SlotClass")]
pub slot_class: String,
#[serde(rename = "SortingClass")]
pub sorting_class: String,
#[serde(rename = "Recipes", default)]
pub recipes: Vec<Recipe>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Recipe {
#[serde(rename = "CreatorPrefabName")]
pub creator_prefab_name: String,
#[serde(rename = "TierName")]
pub tier_name: String,
#[serde(rename = "Time")]
pub time: f64,
#[serde(rename = "Energy")]
pub energy: f64,
#[serde(rename = "Temperature")]
pub temperature: RecipeTemperature,
#[serde(rename = "Pressure")]
pub pressure: RecipePressure,
#[serde(rename = "RequiredMix")]
pub required_mix: RecipeGasMix,
#[serde(rename = "CountTypes")]
pub count_types: i64,
#[serde(flatten)]
pub reagents: indexmap::IndexMap<String, f64>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RecipeTemperature {
#[serde(rename = "Start")]
pub start: f64,
#[serde(rename = "Stop")]
pub stop: f64,
#[serde(rename = "IsValid")]
pub is_valid: bool,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RecipePressure {
#[serde(rename = "Start")]
pub start: f64,
#[serde(rename = "Stop")]
pub stop: f64,
#[serde(rename = "IsValid")]
pub is_valid: bool,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct RecipeGasMix {
#[serde(rename = "Rule")]
pub rule: i64,
#[serde(rename = "IsAny")]
pub is_any: bool,
#[serde(rename = "IsAnyToRemove")]
pub is_any_to_remove: bool,
#[serde(flatten)]
pub reagents: BTreeMap<String, f64>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct Device {
#[serde(rename = "ConnectionList")]
pub connection_list: Vec<(String, String)>,
#[serde(rename = "DevicesLength")]
pub devices_length: Option<i64>,
#[serde(rename = "HasActivateState")]
pub has_activate_state: bool,
#[serde(rename = "HasAtmosphere")]
pub has_atmosphere: bool,
#[serde(rename = "HasColorState")]
pub has_color_state: bool,
#[serde(rename = "HasLockState")]
pub has_lock_state: bool,
#[serde(rename = "HasModeState")]
pub has_mode_state: bool,
#[serde(rename = "HasOnOffState")]
pub has_on_off_state: bool,
#[serde(rename = "HasOpenState")]
pub has_open_state: bool,
#[serde(rename = "HasReagents")]
pub has_reagents: bool,
}