From f13f4fe7d97ec8360ec7a81216794402e25518c2 Mon Sep 17 00:00:00 2001 From: Jovan Gerodetti Date: Tue, 30 Jan 2024 22:58:04 +0100 Subject: [PATCH] Port buildings node to rust (#12) * Port buildings node to rust * clippy lints --- native/Cargo.lock | 12 + native/Cargo.toml | 1 + native/src/lib.rs | 3 + native/src/objects.rs | 1 + native/src/objects/scene_object_registry.rs | 500 ++++++++++++++++++++ native/src/scripts/mod.rs | 1 + native/src/scripts/world.rs | 1 + native/src/scripts/world/buildings.rs | 305 ++++++++++++ native/src/util.rs | 1 + native/src/util/logger.rs | 43 ++ native/src/world.rs | 1 + native/src/world/city_coords_feature.rs | 47 ++ resources/main.tscn | 4 +- src/Objects/World/Buildings.gd | 132 ------ src/Objects/World/World.gd | 6 +- 15 files changed, 922 insertions(+), 136 deletions(-) create mode 100644 native/src/objects.rs create mode 100644 native/src/objects/scene_object_registry.rs create mode 100644 native/src/scripts/world.rs create mode 100644 native/src/scripts/world/buildings.rs create mode 100644 native/src/util.rs create mode 100644 native/src/util/logger.rs create mode 100644 native/src/world.rs create mode 100644 native/src/world/city_coords_feature.rs delete mode 100644 src/Objects/World/Buildings.gd diff --git a/native/Cargo.lock b/native/Cargo.lock index ade7b83..6534d0f 100644 --- a/native/Cargo.lock +++ b/native/Cargo.lock @@ -136,6 +136,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derive-debug" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53ef7e1cf756fd5a8e74b9a0a9504ec446eddde86c3063a76ff26a13b7773b1" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "either" version = "1.6.1" @@ -408,6 +419,7 @@ name = "native" version = "0.4.0" dependencies = [ "backtrace", + "derive-debug", "godot", "godot-rust-script", "itertools", diff --git a/native/Cargo.toml b/native/Cargo.toml index 522d76d..1f5aaaf 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -14,5 +14,6 @@ num = "0.4.0" rayon = "1.5.1" itertools = "0.10.3" num_enum = "0.7.1" +derive-debug = "0.1.2" godot-rust-script = { git = "https://github.com/titannano/godot-rust-script", rev = "e06373cb0d8efce3ec097bbedde3e98e08a9ebfa" } diff --git a/native/src/lib.rs b/native/src/lib.rs index d6676a1..93d0535 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -1,5 +1,8 @@ +mod objects; mod scripts; mod terrain_builder; +mod util; +mod world; use godot::prelude::{gdextension, ExtensionLibrary, InitLevel}; diff --git a/native/src/objects.rs b/native/src/objects.rs new file mode 100644 index 0000000..a1ef2f3 --- /dev/null +++ b/native/src/objects.rs @@ -0,0 +1 @@ +pub mod scene_object_registry; diff --git a/native/src/objects/scene_object_registry.rs b/native/src/objects/scene_object_registry.rs new file mode 100644 index 0000000..3511c63 --- /dev/null +++ b/native/src/objects/scene_object_registry.rs @@ -0,0 +1,500 @@ +#![allow(dead_code)] + +use godot::{ + builtin::meta::ToGodot, + engine::{PackedScene, ResourceLoader}, + log::godot_warn, + obj::Gd, +}; +use num_enum::TryFromPrimitive; + +#[derive(TryFromPrimitive)] +#[repr(u8)] +enum Powerlines { + LeftRight = 0x0E, + TopBottom = 0x0F, + HighTopBottom = 0x10, + LeftHighRight = 0x11, + TopHighBottom = 0x12, + HighLeftRight = 0x13, + BottomRight = 0x14, + BottomLeft = 0x15, + TopLeft = 0x16, + TopRight = 0x17, + RightTopBottom = 0x18, + LeftBottomRight = 0x19, + TopLeftBottom = 0x1A, + LeftTopRight = 0x1B, + LeftTopBottomRight = 0x1C, + BridgeTopBottom = 0x5C, +} + +impl Powerlines { + fn as_str(&self) -> &'static str { + match self { + Powerlines::LeftRight => "res://resources/Objects/Networks/Powerline/left_right.tscn", + Powerlines::TopBottom => "res://resources/Objects/Networks/Powerline/top_bottom.tscn", + Powerlines::HighTopBottom => { + "res://resources/Objects/Networks/Powerline/high_top_bottom.tscn" + } + Powerlines::LeftHighRight => { + "res://resources/Objects/Networks/Powerline/left_high_right.tscn" + } + Powerlines::TopHighBottom => { + "res://resources/Objects/Networks/Powerline/top_high_bottom.tscn" + } + Powerlines::HighLeftRight => { + "res://resources/Objects/Networks/Powerline/high_left_right.tscn" + } + Powerlines::BottomRight => { + "res://resources/Objects/Networks/Powerline/bottom_right.tscn" + } + Powerlines::BottomLeft => "res://resources/Objects/Networks/Powerline/bottom_left.tscn", + Powerlines::TopLeft => "res://resources/Objects/Networks/Powerline/top_left.tscn", + Powerlines::TopRight => "res://resources/Objects/Networks/Powerline/top_right.tscn", + Powerlines::RightTopBottom => { + "res://resources/Objects/Networks/Powerline/right_top_bottom.tscn" + } + Powerlines::LeftBottomRight => { + "res://resources/Objects/Networks/Powerline/left_bottom_right.tscn" + } + Powerlines::TopLeftBottom => { + "res://resources/Objects/Networks/Powerline/top_left_bottom.tscn" + } + Powerlines::LeftTopRight => { + "res://resources/Objects/Networks/Powerline/left_top_right.tscn" + } + Powerlines::LeftTopBottomRight => { + "res://resources/Objects/Networks/Powerline/left_top_bottom_right.tscn" + } + Powerlines::BridgeTopBottom => { + "res://resources/Objects/Networks/Powerline/bridge_top_bottom.tscn" + } + } + } +} + +#[derive(TryFromPrimitive)] +#[repr(u8)] +enum Road { + LeftRight = 0x1D, + TopBottom = 0x1E, + HighTopBottom = 0x1F, + LeftHighRight = 0x20, + TopHighBottom = 0x21, + HighLeftRight = 0x22, + TopRight = 0x23, + BottomRight = 0x24, + BottomLeft = 0x25, + TopLeft = 0x26, + RightTopBottom = 0x27, + LeftBottomRight = 0x28, + TopLeftBottom = 0x29, + LeftTopRight = 0x2A, + LeftTopBottomRight = 0x2B, + LeftRightPowerTopBottom = 0x43, + TopBottomPowerLeftRight = 0x44, +} + +impl Road { + fn as_str(&self) -> &'static str { + match self { + Self::LeftRight => "res://resources/Objects/Networks/Road/left_right.tscn", + Self::TopBottom => "res://resources/Objects/Networks/Road/top_bottom.tscn", + Self::HighTopBottom => "res://resources/Objects/Networks/Road/high_top_bottom.tscn", + Self::LeftHighRight => "res://resources/Objects/Networks/Road/left_high_right.tscn", + Self::TopHighBottom => "res://resources/Objects/Networks/Road/top_high_bottom.tscn", + Self::HighLeftRight => "res://resources/Objects/Networks/Road/high_left_right.tscn", + Self::TopRight => "res://resources/Objects/Networks/Road/top_right.tscn", + Self::BottomRight => "res://resources/Objects/Networks/Road/bottom_right.tscn", + Self::BottomLeft => "res://resources/Objects/Networks/Road/bottom_left.tscn", + Self::TopLeft => "res://resources/Objects/Networks/Road/top_left.tscn", + Self::RightTopBottom => "res://resources/Objects/Networks/Road/right_top_bottom.tscn", + Self::LeftBottomRight => "res://resources/Objects/Networks/Road/left_bottom_right.tscn", + Self::TopLeftBottom => "res://resources/Objects/Networks/Road/top_left_bottom.tscn", + Self::LeftTopRight => "res://resources/Objects/Networks/Road/left_top_right.tscn", + Self::LeftTopBottomRight => { + "res://resources/Objects/Networks/Road/left_top_bottom_right.tscn" + } + Self::LeftRightPowerTopBottom => { + "res://resources/Objects/Networks/Road/left_right_power_top_bottom.tscn" + } + Self::TopBottomPowerLeftRight => { + "res://resources/Objects/Networks/Road/top_bottom_power_left_right.tscn" + } + } + } +} + +#[derive(TryFromPrimitive)] +#[repr(u8)] +enum SuspensionBridge { + StartBottom = 0x51, + MiddleBottom = 0x52, + Center = 0x53, + MiddleTop = 0x54, + EndTop = 0x55, +} + +impl SuspensionBridge { + fn as_str(&self) -> &'static str { + match self { + Self::StartBottom => { + "res://resources/Objects/Networks/Bridge/bridge_suspension_start_bottom.tscn" + } + Self::MiddleBottom => { + "res://resources/Objects/Networks/Bridge/bridge_suspension_middle_bottom.tscn" + } + Self::Center => "res://resources/Objects/Networks/Bridge/bridge_suspension_center.tscn", + Self::MiddleTop => { + "res://resources/Objects/Networks/Bridge/bridge_suspension_middle_top.tscn" + } + Self::EndTop => { + "res://resources/Objects/Networks/Bridge/bridge_suspension_end_top.tscn" + } + } + } +} + +#[derive(TryFromPrimitive)] +#[repr(u8)] +enum PylonBridge { + RaisingTowerTopBottom = 0x56, + BridgeTopA = 0x57, + BridgeTopB = 0x58, +} + +impl PylonBridge { + fn as_str(&self) -> &'static str { + match self { + Self::RaisingTowerTopBottom => { + "res://resources/Objects/Networks/Bridge/bridge_raising_tower_top_bottom.tscn" + } + Self::BridgeTopA => "res://resources/Objects/Networks/Bridge/bridge_top.tscn", + Self::BridgeTopB => "res://resources/Objects/Networks/Bridge/bridge_top.tscn", + } + } +} + +fn networks(id: u8) -> Option<&'static str> { + // Powerlines + Powerlines::try_from_primitive(id) + .ok() + .as_ref() + .map(Powerlines::as_str) + .or_else(|| Road::try_from_primitive(id).ok().as_ref().map(Road::as_str)) + .or_else(|| { + SuspensionBridge::try_from_primitive(id) + .ok() + .as_ref() + .map(SuspensionBridge::as_str) + }) + .or_else(|| { + PylonBridge::try_from_primitive(id) + .ok() + .as_ref() + .map(PylonBridge::as_str) + }) +} + +#[derive(TryFromPrimitive, Clone, Copy)] +#[repr(u8)] +pub enum Buildings { + ParkSmall = 0x0D, + TreeSingle = 0x06, + HomeMiddleClass1 = 0x73, + HomeMiddleClass2 = 0x74, + HomeMiddleClass3 = 0x75, + HomeMiddleClass4 = 0x76, + HomeMiddleClass5 = 0x77, + Church = 0xF7, + OfficeBuildingMedium1 = 0x96, + OfficeBuildingMedium2 = 0x98, + OfficeBuildingMedium3 = 0x9A, + OfficeBuildingMedium4 = 0x9B, + OfficeBuildingMedium5 = 0x9C, + OfficeBuildingMedium6 = 0x9D, + AbandonedBuilding1 = 0x8A, + AbandonedBuilding2 = 0x8B, + AbandonedBuilding3 = 0xAA, + AbandonedBuilding4 = 0xAB, + AbandonedBuilding5 = 0xAC, + AbandonedBuilding6 = 0xAD, + HomeUpperClass1 = 0x78, + HomeUpperClass2 = 0x79, + HomeUpperClass3 = 0x7A, + HomeUpperClass4 = 0x7B, + Tarmac = 0xE6, + TarmacRadar = 0xEA, + Construction1 = 0xC2, + Construction2 = 0xC3, + Construction3 = 0xA6, + Construction4 = 0xA7, + Construction5 = 0xA8, + Construction6 = 0xA9, + Construction7 = 0x88, + Construction8 = 0x89, + AirportWarehouse = 0xE3, + AirportBuilding1 = 0xE4, + AirportBuilding2 = 0xE5, + AirportHangar1 = 0xE8, + AirportRunway = 0xDD, + AirportRunwayIntersection = 0xDF, + Hangar2 = 0xF6, + CondominiumsMedium1 = 0x91, + CondominiumsMedium2 = 0x92, + CondominiumsMedium3 = 0x93, + CondominiumsLarge1 = 0xB0, + CondominiumsLarge2 = 0xB1, + FactorySmall1 = 0xA0, + FactorySmall2 = 0xA1, + FactorySmall3 = 0xA2, + FactorySmall4 = 0xA3, + FactorySmall5 = 0xA4, + FactorySmall6 = 0xA5, + StationPolice = 0xD2, + ApartmentsMedium1 = 0x8F, + ApartmentsMedium2 = 0x90, + ToyStore = 0x83, + IndustrialSubstation = 0x87, + OfficesSmall1 = 0x80, + OfficesSmall2 = 0x81, + OfficesHistoric = 0xBA, + WaterPump = 0xDC, + StationHospital = 0xD1, + ConvenienceStore = 0x7E, + StationGas1 = 0x7C, + StationGas2 = 0x7F, + HomeLowerClass1 = 0x70, + HomeLowerClass2 = 0x71, + HomeLowerClass3 = 0x72, + Warehouse = 0x82, + AirportCivilianControlTower = 0xE1, + StationFire = 0xD3, + PowerplantMicrowave = 0xCD, + ResortHotel = 0x97, + ApartmentsLarge1 = 0xAE, + ApartmentsLarge2 = 0xAF, + ApartmentsSmall1 = 0x8C, + ApartmentsSmall2 = 0x8D, + ApartmentsSmall3 = 0x8E, + TreeCouple = 0x07, + ChemicalStorage = 0x85, + ChemicalProcessing1 = 0xBC, + ChemicalProcessing2 = 0x9F, + School = 0xD6, + Library = 0xF5, + Marina = 0xF8, + WarehouseLarge1 = 0xC0, + WarehouseLarge2 = 0xC1, + WarehouseSmall1 = 0x84, + WarehouseSmall2 = 0x86, + WarehouseMedium = 0x9E, + BbInn = 0x7D, + College = 0xD9, + ArcologyPlymouth = 0xFB, + ArcologyForest = 0xFC, + ArcologyDarco = 0xFD, + ArcologyLaunch = 0xFE, + MayorsHouse = 0xF3, + Museum = 0xD4, + OfficeRetail = 0x99, + ParkingLot = 0xB9, + ShoppingCentre = 0x94, + Theatre = 0xB5, + WaterTreatment = 0xF4, +} + +impl Buildings { + fn as_str(&self) -> &'static str { + match self { + Self::ParkSmall => "res://resources/Objects/Buildings/park_small.tscn", + Self::TreeSingle => "res://resources/Objects/Buildings/tree_single.tscn", + Self::HomeMiddleClass1 => "res://resources/Objects/Buildings/home_middle_class_1.tscn", + Self::HomeMiddleClass2 => "res://resources/Objects/Buildings/home_middle_class_2.tscn", + Self::HomeMiddleClass3 => "res://resources/Objects/Buildings/home_middle_class_3.tscn", + Self::HomeMiddleClass4 => "res://resources/Objects/Buildings/home_middle_class_4.tscn", + Self::HomeMiddleClass5 => "res://resources/Objects/Buildings/home_middle_class_5.tscn", + Self::Church => "res://resources/Objects/Buildings/church.tscn", + Self::OfficeBuildingMedium1 => { + "res://resources/Objects/Buildings/office_building_medium_1.tscn" + } + Self::OfficeBuildingMedium2 => { + "res://resources/Objects/Buildings/office_building_medium_2.tscn" + } + Self::OfficeBuildingMedium3 => { + "res://resources/Objects/Buildings/office_building_medium_3.tscn" + } + Self::OfficeBuildingMedium4 => { + "res://resources/Objects/Buildings/office_building_medium_4.tscn" + } + Self::OfficeBuildingMedium5 => { + "res://resources/Objects/Buildings/office_building_medium_5.tscn" + } + Self::OfficeBuildingMedium6 => { + "res://resources/Objects/Buildings/office_building_medium_6.tscn" + } + Self::AbandonedBuilding1 => { + "res://resources/Objects/Buildings/abandoned_building_1.tscn" + } + Self::AbandonedBuilding2 => { + "res://resources/Objects/Buildings/abandoned_building_2.tscn" + } + Self::AbandonedBuilding3 => { + "res://resources/Objects/Buildings/abandoned_building_3.tscn" + } + Self::AbandonedBuilding4 => { + "res://resources/Objects/Buildings/abandoned_building_4.tscn" + } + Self::AbandonedBuilding5 => { + "res://resources/Objects/Buildings/abandoned_building_5.tscn" + } + Self::AbandonedBuilding6 => { + "res://resources/Objects/Buildings/abandoned_building_6.tscn" + } + Self::HomeUpperClass1 => "res://resources/Objects/Buildings/home_upper_class_1.tscn", + Self::HomeUpperClass2 => "res://resources/Objects/Buildings/home_upper_class_2.tscn", + Self::HomeUpperClass3 => "res://resources/Objects/Buildings/home_upper_class_3.tscn", + Self::HomeUpperClass4 => "res://resources/Objects/Buildings/home_upper_class_4.tscn", + Self::Tarmac => "res://resources/Objects/Ground/tarmac.tscn", + Self::TarmacRadar => "res://resources/Objects/Buildings/tarmac_radar.tscn", + Self::Construction1 => "res://resources/Objects/Buildings/construction_1-2.tscn", + Self::Construction2 => "res://resources/Objects/Buildings/construction_1-2.tscn", + Self::Construction3 => "res://resources/Objects/Buildings/construction_3.tscn", + Self::Construction4 => "res://resources/Objects/Buildings/construction_4.tscn", + Self::Construction5 => "res://resources/Objects/Buildings/construction_5.tscn", + Self::Construction6 => "res://resources/Objects/Buildings/construction_6.tscn", + Self::Construction7 => "res://resources/Objects/Buildings/construction_7.tscn", + Self::Construction8 => "res://resources/Objects/Buildings/construction_8.tscn", + Self::AirportWarehouse => "res://resources/Objects/Buildings/airport_warehouse.tscn", + Self::AirportBuilding1 => "res://resources/Objects/Buildings/airport_building_1.tscn", + Self::AirportBuilding2 => "res://resources/Objects/Buildings/airport_building_2.tscn", + Self::AirportHangar1 => "res://resources/Objects/Buildings/airport_hangar_1.tscn", + Self::AirportRunway => "res://resources/Objects/Buildings/airport_runway.tscn", + Self::AirportRunwayIntersection => { + "res://resources/Objects/Buildings/airport_runway_intersection.tscn" + } + Self::Hangar2 => "res://resources/Objects/Buildings/hangar_2.tscn", + Self::CondominiumsMedium1 => { + "res://resources/Objects/Buildings/condominiums_medium_1.tscn" + } + Self::CondominiumsMedium2 => { + "res://resources/Objects/Buildings/condominiums_medium_2.tscn" + } + Self::CondominiumsMedium3 => { + "res://resources/Objects/Buildings/condominiums_medium_3.tscn" + } + Self::CondominiumsLarge1 => { + "res://resources/Objects/Buildings/condominiums_large_1.tscn" + } + Self::CondominiumsLarge2 => { + "res://resources/Objects/Buildings/condominiums_large_2.tscn" + } + Self::FactorySmall1 => "res://resources/Objects/Buildings/factory_small_1.tscn", + Self::FactorySmall2 => "res://resources/Objects/Buildings/factory_small_2.tscn", + Self::FactorySmall3 => "res://resources/Objects/Buildings/factory_small_3.tscn", + Self::FactorySmall4 => "res://resources/Objects/Buildings/factory_small_4.tscn", + Self::FactorySmall5 => "res://resources/Objects/Buildings/factory_small_5.tscn", + Self::FactorySmall6 => "res://resources/Objects/Buildings/factory_small_6.tscn", + Self::StationPolice => "res://resources/Objects/Buildings/station_police.tscn", + Self::ApartmentsMedium1 => "res://resources/Objects/Buildings/apartments_medium_1.tscn", + Self::ApartmentsMedium2 => "res://resources/Objects/Buildings/apartments_medium_2.tscn", + Self::ToyStore => "res://resources/Objects/Buildings/toy_store.tscn", + Self::IndustrialSubstation => { + "res://resources/Objects/Buildings/industrial_substation.tscn" + } + Self::OfficesSmall1 => "res://resources/Objects/Buildings/offices_small_1.tscn", + Self::OfficesSmall2 => "res://resources/Objects/Buildings/offices_small_2.tscn", + Self::OfficesHistoric => "res://resources/Objects/Buildings/offices_historic.tscn", + Self::WaterPump => "res://resources/Objects/Buildings/water_pump.tscn", + Self::StationHospital => "res://resources/Objects/Buildings/station_hospital.tscn", + Self::ConvenienceStore => "res://resources/Objects/Buildings/convenience_store.tscn", + Self::StationGas1 => "res://resources/Objects/Buildings/station_gas_1.tscn", + Self::StationGas2 => "res://resources/Objects/Buildings/station_gas_2.tscn", + Self::HomeLowerClass1 => "res://resources/Objects/Buildings/home_lower_class_1.tscn", + Self::HomeLowerClass2 => "res://resources/Objects/Buildings/home_lower_class_2.tscn", + Self::HomeLowerClass3 => "res://resources/Objects/Buildings/home_lower_class_3.tscn", + Self::Warehouse => "res://resources/Objects/Buildings/warehouse.tscn", + Self::AirportCivilianControlTower => { + "res://resources/Objects/Buildings/airport_civilian_control_tower.tscn" + } + Self::StationFire => "res://resources/Objects/Buildings/station_fire.tscn", + Self::PowerplantMicrowave => { + "res://resources/Objects/Buildings/powerplant_microwave.tscn" + } + Self::ResortHotel => "res://resources/Objects/Buildings/resort_hotel.tscn", + Self::ApartmentsLarge1 => "res://resources/Objects/Buildings/apartments_large_1.tscn", + Self::ApartmentsLarge2 => "res://resources/Objects/Buildings/apartments_large_2.tscn", + Self::ApartmentsSmall1 => "res://resources/Objects/Buildings/apartments_small_1.tscn", + Self::ApartmentsSmall2 => "res://resources/Objects/Buildings/apartments_small_2.tscn", + Self::ApartmentsSmall3 => "res://resources/Objects/Buildings/apartments_small_3.tscn", + Self::TreeCouple => "res://resources/Objects/Buildings/tree_couple.tscn", + Self::ChemicalStorage => "res://resources/Objects/Buildings/chemical_storage.tscn", + Self::ChemicalProcessing1 => { + "res://resources/Objects/Buildings/chemical_processing_1.tscn" + } + Self::ChemicalProcessing2 => { + "res://resources/Objects/Buildings/chemical_processing_2.tscn" + } + Self::School => "res://resources/Objects/Buildings/school.tscn", + Self::Library => "res://resources/Objects/Buildings/library.tscn", + Self::Marina => "res://resources/Objects/Buildings/marina.tscn", + Self::WarehouseLarge1 => "res://resources/Objects/Buildings/warehouse_large_1.tscn", + Self::WarehouseLarge2 => "res://resources/Objects/Buildings/warehouse_large_2.tscn", + Self::WarehouseSmall1 => "res://resources/Objects/Buildings/warehouse_small_1.tscn", + Self::WarehouseSmall2 => "res://resources/Objects/Buildings/warehouse_small_2.tscn", + Self::WarehouseMedium => "res://resources/Objects/Buildings/warehouse_medium.tscn", + Self::BbInn => "res://resources/Objects/Buildings/bb_inn.tscn", + Self::College => "res://resources/Objects/Buildings/college.tscn", + Self::ArcologyPlymouth => "res://resources/Objects/Buildings/arcology_plymouth.tscn", + Self::ArcologyForest => "res://resources/Objects/Buildings/arcology_forest.tscn", + Self::ArcologyDarco => "res://resources/Objects/Buildings/arcology_darco.tscn", + Self::ArcologyLaunch => "res://resources/Objects/Buildings/arcology_launch.tscn", + Self::MayorsHouse => "res://resources/Objects/Buildings/mayors_house.tscn", + Self::Museum => "res://resources/Objects/Buildings/museum.tscn", + Self::OfficeRetail => "res://resources/Objects/Buildings/office_retail.tscn", + Self::ParkingLot => "res://resources/Objects/Buildings/parking_lot.tscn", + Self::ShoppingCentre => "res://resources/Objects/Buildings/shopping_centre.tscn", + Self::Theatre => "res://resources/Objects/Buildings/theatre.tscn", + Self::WaterTreatment => "res://resources/Objects/Buildings/water_treatment.tscn", + } + } +} + +impl PartialEq for Buildings { + fn eq(&self, other: &u8) -> bool { + *self as u8 == *other + } +} + +impl PartialEq for u8 { + fn eq(&self, other: &Buildings) -> bool { + other == self + } +} + +fn buildings(id: u8) -> Option<&'static str> { + Buildings::try_from_primitive(id) + .ok() + .as_ref() + .map(Buildings::as_str) +} + +fn load(parser: fn(u8) -> Option<&'static str>, object_id: u8) -> Option> { + let Some(object) = parser(object_id) else { + godot_warn!("{:02x} is not a valid object id", object_id); + return None; + }; + + ResourceLoader::singleton() + .load(object.to_godot()) + .map(Gd::cast) +} + +pub fn load_network(object_id: u8) -> Option> { + load(networks, object_id) +} + +pub fn load_building(object_id: u8) -> Option> { + load(buildings, object_id) +} diff --git a/native/src/scripts/mod.rs b/native/src/scripts/mod.rs index e73e999..3f7c780 100644 --- a/native/src/scripts/mod.rs +++ b/native/src/scripts/mod.rs @@ -1,4 +1,5 @@ mod particles; mod spawner; +mod world; godot_rust_script::setup_library!(); diff --git a/native/src/scripts/world.rs b/native/src/scripts/world.rs new file mode 100644 index 0000000..326ca11 --- /dev/null +++ b/native/src/scripts/world.rs @@ -0,0 +1 @@ +mod buildings; diff --git a/native/src/scripts/world/buildings.rs b/native/src/scripts/world/buildings.rs new file mode 100644 index 0000000..e3afff8 --- /dev/null +++ b/native/src/scripts/world/buildings.rs @@ -0,0 +1,305 @@ +use std::{collections::VecDeque, fmt::Debug, ops::Not}; + +use derive_debug::Dbg; +use godot::{ + builtin::{meta::ToGodot, Array, Dictionary, VariantArray}, + engine::{utilities::snappedi, Node, Node3D, NodeExt, Resource, Time}, + log::{godot_error, godot_print}, + obj::{Gd, NewAlloc}, +}; +use godot_rust_script::{godot_script_impl, GodotScript, ScriptSignal, Signal}; + +use crate::{info, objects::scene_object_registry, world::city_coords_feature::CityCoordsFeature}; + +#[derive(GodotScript, Debug)] +#[script(base = Node)] +struct Buildings { + #[export] + pub world_constants: Option>, + + city_coords_feature: CityCoordsFeature, + job_runner: Option>, + + /// tile_coords, size, altitude + #[signal] + spawn_point_encountered: Signal<(Array, u8, u32)>, + + #[signal] + loading_progress: Signal, + + #[signal] + ready: Signal<()>, + + base: Gd, +} + +#[godot_script_impl] +impl Buildings { + pub fn _ready(&mut self) {} + + pub fn _process(&mut self, _delta: f64) { + if let Some(mut job_runner) = self.job_runner.take() { + let progress = job_runner.poll(self); + + self.job_runner = Some(job_runner); + + match progress { + 0 => self.ready.emit(()), + progress => self.loading_progress.emit(progress), + } + } + } + + pub fn build_async(&mut self, city: Dictionary) { + let simulator_settings: Dictionary = city.get_or_nil("simulator_settings").to(); + let sea_level: u32 = simulator_settings.get_or_nil("GlobalSeaLevel").to(); + let buildings: Dictionary = city.get_or_nil("buildings").to(); + let tiles: Dictionary = city.get_or_nil("tilelist").to(); + + self.city_coords_feature = CityCoordsFeature::new( + self.world_constants + .as_ref() + .expect("world_constants should be set!") + .to_owned(), + sea_level, + ); + + info!("starting to load buildings..."); + + let mut job_runner = LocalJobRunner::new( + move |host: &mut Self, building: Dictionary| { + if building.get_or_nil("building_id").to::() == 0x00 { + info!("skipping empty building"); + return; + } + + host.insert_building(building, &tiles); + }, + 50, + ); + + let buildings_array = buildings + .values_array() + .iter_shared() + .map(|variant| variant.to()) + .collect(); + + job_runner.tasks(buildings_array); + + self.job_runner = Some(job_runner); + } + + fn insert_building(&mut self, building: Dictionary, tiles: &Dictionary) { + let building_size: u8 = building + .get("size") + .expect("insert_building: missing size") + .to(); + let name: String = building + .get("name") + .expect("insert_building: missing name") + .to(); + let building_id: u8 = building + .get("building_id") + .expect("insert_building: missing building_id") + .to(); + let object = scene_object_registry::load_building(building_id); + let tile_coords: Array = building + .get("tile_coords") + .expect("insert_building: missing tile_coords") + .to::() + .iter_shared() + .map(|value| value.to()) + .collect(); + let tile: Dictionary = tiles + .get(tile_coords.clone()) + .expect("insert_building: missing tile") + .to(); + let altitude: u32 = tile + .get("altitude") + .expect("insert_building: missing altitude") + .to(); + + let Some(object) = object else { + godot_error!("unknown building \"{}\"", name); + return; + }; + + if building_id == scene_object_registry::Buildings::Tarmac + && is_spawn_point(&building, tiles) + { + info!("encountered a spawn point: {:?}", building); + let mut spawn_building = Dictionary::new(); + + spawn_building.set( + "building_id", + scene_object_registry::Buildings::Hangar2 as u8, + ); + + spawn_building.set( + "tile_coords", + tile_coords + .iter_shared() + .map(|v| v.to_variant()) + .collect::(), + ); + spawn_building.set("name", "Hangar"); + spawn_building.set("size", 2); + spawn_building.set("altitude", altitude); + + self.insert_building(spawn_building, tiles); + self.spawn_point_encountered + .emit((tile_coords.clone(), 2, altitude)); + } + + let (Some(instance), instance_time) = with_timing(|| object.instantiate()) else { + godot_error!("failed to instantiate building {}", name); + return; + }; + + let mut location = self.city_coords_feature.get_building_coords( + tile_coords.get(0), + tile_coords.get(1), + altitude, + building_size, + ); + + // fix z fighting of flat buildings + location.y += 0.1; + + let sector_name = { + let x = snappedi(tile_coords.get(0) as f64, 10); + let y = snappedi(tile_coords.get(1) as f64, 10); + + format!("{}_{}", x, y) + }; + + let (_, insert_time) = with_timing(|| { + self.get_sector(sector_name) + .add_child_ex(instance.clone()) + .force_readable_name(true) + .done() + }); + + let (_, translate_time) = with_timing(|| instance.cast::().translate(location)); + + if instance_time > 100 { + godot_error!("\"{}\" is very slow to instantiate!", name); + } + + if insert_time > 100 { + godot_error!("\"{}\" is very slow to insert!", name); + } + + if translate_time > 100 { + godot_error!("\"{}\" is very slow to translate!", name); + } + } + + fn get_sector(&mut self, name: String) -> Gd { + self.base + .get_node_or_null(name.to_godot().into()) + .unwrap_or_else(|| { + let mut sector = Node::new_alloc(); + + sector.set_name(name.to_godot()); + + self.base.add_child(sector); + self.base.get_node_as(name) + }) + } +} + +type LocalJob = Box; + +#[derive(Dbg)] +struct LocalJobRunner +where + T: Debug, +{ + budget: u64, + tasks: VecDeque, + #[dbg(skip)] + callback: LocalJob, +} + +impl LocalJobRunner { + fn new(callback: C, budget: u64) -> Self { + Self { + callback: Box::new(callback), + tasks: VecDeque::new(), + budget, + } + } + + fn poll(&mut self, host: &mut H) -> u32 { + let start = Time::singleton().get_ticks_msec(); + let mut count = 0; + + while Time::singleton().get_ticks_msec() - start < self.budget { + let Some(item) = self.tasks.remove(0) else { + return count; + }; + + (self.callback)(host, item); + count += 1; + } + + count + } + + fn tasks(&mut self, mut tasks: VecDeque) { + self.tasks.append(&mut tasks); + } +} + +fn is_spawn_point(building: &Dictionary, tiles: &Dictionary) -> bool { + let tile_coords: Array = building + .get_or_nil("tile_coords") + .to::() + .iter_shared() + .map(|item| item.to()) + .collect(); + let x = tile_coords.get(0); + let y = tile_coords.get(1); + + let x_miss = (x - 1..x + 3) + .all(|index| { + let Some(tile) = tiles.get(VariantArray::from(&[index.to_variant(), y.to_variant()])) + else { + godot_error!("unable to get tile at: x = {}, y = {}", index, y); + return false; + }; + + let tile = tile.to::(); + + tile.get("building") + .and_then(|building| building.try_to::().ok()) + .and_then(|building| building.get("building_id")) + .map(|id| id.to::() == scene_object_registry::Buildings::Tarmac) + .unwrap_or(false) + }) + .not(); + + if x_miss { + return false; + } + + (y - 1..y + 3).all(|index| { + tiles + .get(VariantArray::from(&[x.to_variant(), index.to_variant()])) + .map(|tile| tile.to::()) + .and_then(|tile| tile.get("building")) + .and_then(|building| building.try_to::().ok()) + .and_then(|building| building.get("building_id")) + .map(|id| id.to::() == scene_object_registry::Buildings::Tarmac) + .unwrap_or(false) + }) +} + +fn with_timing(cb: impl FnOnce() -> R) -> (R, u64) { + let start = Time::singleton().get_ticks_msec(); + + let result = cb(); + + (result, Time::singleton().get_ticks_msec() - start) +} diff --git a/native/src/util.rs b/native/src/util.rs new file mode 100644 index 0000000..d991728 --- /dev/null +++ b/native/src/util.rs @@ -0,0 +1 @@ +pub mod logger; diff --git a/native/src/util/logger.rs b/native/src/util/logger.rs new file mode 100644 index 0000000..72925c5 --- /dev/null +++ b/native/src/util/logger.rs @@ -0,0 +1,43 @@ +#[macro_export] +macro_rules! log { + ($level:ident, $($param:expr),+) => { + { + let time = ::godot::engine::Time::singleton().get_ticks_msec(); + let minutes = time / 1000 / 60; + let seconds = time / 1000 % 60; + let milliseconds = time % 1000; + + let message = format!($($param),+); + + godot_print!("[{:03}:{:02}:{:03}] [{}] [{}:{}] {}", minutes, seconds, milliseconds, stringify!($level), file!(), line!(), message); + } + }; +} + +#[macro_export] +macro_rules! debug { + ($($param:expr),+) => { + $crate::log!(DEBUG, $($param),+); + }; +} + +#[macro_export] +macro_rules! info { + ($($param:expr),+) => { + $crate::log!(INFO, $($param),+); + }; +} + +#[macro_export] +macro_rules! warn { + ($($param:expr),+) => { + $crate::log!(WARN, $($param),+); + }; +} + +#[macro_export] +macro_rules! error { + ($($param:expr),+) => { + $crate::log!(ERROR, $($param),+); + }; +} diff --git a/native/src/world.rs b/native/src/world.rs new file mode 100644 index 0000000..f7cac76 --- /dev/null +++ b/native/src/world.rs @@ -0,0 +1 @@ +pub mod city_coords_feature; diff --git a/native/src/world/city_coords_feature.rs b/native/src/world/city_coords_feature.rs new file mode 100644 index 0000000..0dcc3cd --- /dev/null +++ b/native/src/world/city_coords_feature.rs @@ -0,0 +1,47 @@ +use godot::{builtin::Vector3, engine::Resource, log::godot_error, obj::Gd}; + +#[derive(Debug, Default)] +pub struct CityCoordsFeature { + world_constants: Gd, + sea_level: u32, +} + +impl CityCoordsFeature { + pub fn new(world_constants: Gd, sea_level: u32) -> Self { + Self { + world_constants, + sea_level, + } + } + + pub fn get_world_coords(&self, x: u32, y: u32, z: u32) -> Vector3 { + let tile_size: u8 = self.world_constants.get("tile_size".into()).to(); + let tile_height: u32 = self.world_constants.get("tile_height".into()).to(); + + Vector3 { + x: (x * u32::from(tile_size)) as f32, + y: (z.max(self.sea_level - 1) * tile_height) as f32, + z: (y * u32::from(tile_size)) as f32, + } + } + + pub fn get_building_coords(&self, x: u32, y: u32, z: u32, size: u8) -> Vector3 { + let tile_size: u8 = self + .world_constants + .get("tile_size".into()) + .try_to() + .map_err(|err| godot_error!("failed to get tile_size of world_constants: {}", err)) + .unwrap(); + + let offset = (size * tile_size) as f32 / 2.0; + + let y = y - (size as u32 - 1); + + let mut location = self.get_world_coords(x, y, z); + + location.x += offset; + location.z += offset; + + location + } +} diff --git a/resources/main.tscn b/resources/main.tscn index bd74e52..2f2d071 100644 --- a/resources/main.tscn +++ b/resources/main.tscn @@ -14,7 +14,7 @@ [ext_resource type="Material" uid="uid://bmp5rvu5slnnt" path="res://resources/Materials/ocean_material.tres" id="13"] [ext_resource type="Script" path="res://src/Objects/World/Networks.gd" id="14"] [ext_resource type="Resource" path="res://resources/Data/world_constants.tres" id="15"] -[ext_resource type="Script" path="res://src/Objects/World/Buildings.gd" id="16"] +[ext_resource type="Script" path="res://native/src/scripts/world/buildings.rs" id="15_23gpq"] [ext_resource type="PackedScene" uid="uid://cmv7rt4gqew38" path="res://resources/Objects/Helis/schweizer_300.tscn" id="16_e6k8r"] [ext_resource type="Script" path="res://src/Objects/Networks/RoadNavigation.gd" id="17"] @@ -135,7 +135,7 @@ world_constants = ExtResource("15") script = ExtResource("5") [node name="Buildings" type="Node" parent="SubViewportContainer/SubViewport/World"] -script = ExtResource("16") +script = ExtResource("15_23gpq") world_constants = ExtResource("15") [node name="Backdrop" type="Node" parent="SubViewportContainer/SubViewport/World"] diff --git a/src/Objects/World/Buildings.gd b/src/Objects/World/Buildings.gd deleted file mode 100644 index 703dfe3..0000000 --- a/src/Objects/World/Buildings.gd +++ /dev/null @@ -1,132 +0,0 @@ -extends Node - -const TimeBudget := preload("res://src/util/TimeBudget.gd") -const SceneObjectRegistry := preload("res://src/SceneObjectRegistry.gd") -const CityCoordsFeature := preload("res://src/features/CityCoordsFeature.gd") - -signal loading_progress(value) -signal spawn_point_encountered(tile_coords, size, altitude) - -@export var world_constants: WorldConstants - -var city_coords_feature: CityCoordsFeature - -func _ready() -> void: - assert(self.world_constants is WorldConstants, "Buildings.world_constants is not of type WorldConstants") - - -func build_async(city) -> void: - var sea_level: int = city.simulator_settings["GlobalSeaLevel"] - var budget := TimeBudget.new(50) - var buildings: Dictionary = city.buildings - var tiles: Dictionary = city.tilelist - - self.city_coords_feature = CityCoordsFeature.new(self.world_constants, sea_level) - - for key in buildings: - var building: Dictionary = buildings[key] - - if building.building_id == 0x00: - # ignoring empty building - self.emit_signal("loading_progress", 1) - print("skipping empty building") - continue - - self._insert_building(building, tiles) - self.emit_signal("loading_progress", 1) - - if budget.is_exceded(): - print("yielding after ", budget.elapsed(), "ms of work") - budget.restart() - await self.get_tree().process_frame - - print("finished buildings after ", budget.elapsed(), "ms of work") - await self.get_tree().process_frame - -func _is_spawn_point(building: Dictionary, tiles: Dictionary) -> bool: - var x: int = building.tile_coords[0] - var y: int = building.tile_coords[1] - - for index in range(x - 1, x + 3): - var tile: Dictionary = tiles[[index, y]] - - if tile.building == null: - return false - - if tile.building.building_id == 0xE6: - continue - - return false - - for index in range(y - 1, y + 3): - var tile: Dictionary = tiles[[x, index]] - - if tile.building == null: - return false - - if tile.building.building_id == 0xE6: - continue - - return false - - return true - - -func _insert_building(building: Dictionary, tiles: Dictionary) -> void: - var budget := TimeBudget.new(0) - var tile: Dictionary = tiles[building.get("tile_coords") as Array] - var building_size: int = building.size - @warning_ignore("shadowed_variable_base_class") - var name: String = building.name - var object := SceneObjectRegistry.load_building(building.get("building_id") as int) - - if not object: - print("unknown building \"%s\"" % name) - return - - if building.building_id == 0xE6 and self._is_spawn_point(building, tiles): - self._insert_building({ "building_id": 0xF6, "tile_coords": building.tile_coords, "name": "Hangar", "size": 2 }, tiles) - self.spawn_point_encountered.emit(to_int_array(building.get("tile_coords") as Array), 2, tile.altitude) - - budget.restart() - var instance: Node3D = object.instantiate() - var instance_time := budget.elapsed() - var tile_coords: Array[int] = to_int_array(building.get("tile_coords") as Array) - var altitude: int = tile.get("altitude") - - var location := self.city_coords_feature.get_building_coords(tile_coords[0], tile_coords[1], altitude, building_size) - - location.y += 0.1 - - var sector_name := "{x}_{y}".format({ - "x": snapped(building.tile_coords[0], 10), - "y": snapped(building.tile_coords[1], 10) - }) - - budget.restart() - if self.get_node_or_null(sector_name) == null: - var sector := Node.new() - sector.name = sector_name - self.add_child(sector) - - self \ - .get_node_or_null(sector_name) \ - .add_child(instance, true) - var insert_time := budget.elapsed() - - instance.translate(location) - - if instance_time > 100: - printerr("\"%s\" is very slow to instanciate" % building.name) - - if insert_time > 100: - printerr("\"%s\" is very slow to insert" % building.name) - - -static func to_int_array(list: Array) -> Array[int]: - var result: Array[int] = [] - - for item in list: - result.push_back(item) - - return result diff --git a/src/Objects/World/World.gd b/src/Objects/World/World.gd index 959ff6c..b0da57a 100644 --- a/src/Objects/World/World.gd +++ b/src/Objects/World/World.gd @@ -1,3 +1,4 @@ +@uid("uid://v2mac8r6ox4f") # Generated automatically, do not modify. extends Node3D const TimeBudget := preload("../../util/TimeBudget.gd") @@ -6,7 +7,6 @@ const SceneObjectRegistry := preload("res://src/SceneObjectRegistry.gd") const Networks := preload("res://src/Objects/World/Networks.gd") const CityCoordsFeature := preload("res://src/features/CityCoordsFeature.gd") const Logger := preload("res://src/util/Logger.gd") -const Buildings := preload("res://src/Objects/World/Buildings.gd") const Terrain := preload("res://src/Objects/Terrain/Terrain.gd") const Backdrop := preload("res://src/Objects/World/Backdrop.gd") const Helicopter := preload("res://src/Objects/Helicopters/Helicopter.gd") @@ -62,7 +62,9 @@ func _on_spawn_point_encountered(tile_coords: Array[int], size: int, altitude: i func _load_map_async(city: Dictionary): await self.terrain.build_async(city) await self.networks.build_async(city) - await self.buildings.build_async(city) + self.buildings.build_async(city) + + await self.buildings.ready var city_size: int = city.get("city_size")