Skip to content

Commit

Permalink
Async level loading in rust
Browse files Browse the repository at this point in the history
  • Loading branch information
TitanNano committed Aug 30, 2024
1 parent 8b873f9 commit 97f74fe
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 193 deletions.
4 changes: 2 additions & 2 deletions native/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ rand = "0.8.5"
pomsky-macro = "0.11.0"
regex = "1.10.5"

godot-rust-script = { git = "https://github.com/titannano/godot-rust-script", rev = "657a6b15d19b702b3aa6ad0fd23986ceb8598562" }
godot-rust-script = { git = "https://github.com/titannano/godot-rust-script", rev = "ca371c120774412e4a222ba0362fdf4f5e844b6f" }
217 changes: 99 additions & 118 deletions native/src/scripts/world/buildings.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,55 @@
use std::{
collections::{BTreeMap, VecDeque},
fmt::Debug,
ops::Not,
};
use std::{collections::BTreeMap, ops::Not};

use anyhow::Context;
use anyhow::Context as _;
use derive_debug::Dbg;
use godot::{
builtin::{meta::ToGodot, Array, Dictionary},
engine::{Marker3D, Node, Node3D, Resource, Time},
obj::{Gd, NewAlloc},
};
use godot_rust_script::{godot_script_impl, GodotScript, ScriptSignal, Signal};

use crate::{
objects::scene_object_registry,
util::logger,
world::{
city_coords_feature::CityCoordsFeature,
city_data::{self, TryFromDictionary},
},
use godot::builtin;
use godot::builtin::meta::ToGodot;
use godot::builtin::{Array, Dictionary};
use godot::engine::{Marker3D, Node, Node3D, Resource, Time};
use godot::obj::{Gd, NewAlloc};
use godot_rust_script::{
godot_script_impl, CastToScript, GodotScript, RsRef, ScriptSignal, Signal,
};

#[derive(GodotScript, Debug)]
use crate::objects::scene_object_registry;
use crate::util::async_support::{self, godot_task, GodotFuture, TaskHandle, ToSignalFuture};
use crate::util::logger;
use crate::world::city_coords_feature::CityCoordsFeature;
use crate::world::city_data::{self, TryFromDictionary};

#[derive(GodotScript, Dbg)]
#[script(base = Node)]
struct Buildings {
#[dbg(skip)]
pending_build_tasks: Vec<TaskHandle>,

#[export]
pub world_constants: Option<Gd<Resource>>,

city_coords_feature: CityCoordsFeature,
job_runner: Option<LocalJobRunner<city_data::Building, Self>>,

/// tile_coords, size, altitude
#[signal]
pub spawn_point_encountered: Signal<(Array<u32>, u8, u32)>,

#[signal]
pub loading_progress: Signal<u32>,

#[signal]
pub ready: Signal<()>,

base: Gd<Node>,
}

#[godot_script_impl]
impl Buildings {
const TIME_BUDGET: u64 = 50;

pub fn _process(&mut self, _delta: f64) {
if let Some(mut job_runner) = self.job_runner.take() {
let progress = job_runner.poll(self);
self.pending_build_tasks.retain(|task| task.is_pending());

self.job_runner = Some(job_runner);
let tasks = self.pending_build_tasks.len();

match progress {
0 => self.ready.emit(()),
progress => self.loading_progress.emit(progress),
}
if tasks > 0 {
logger::debug!(
"World Buildings Node: {} active tasks!",
self.pending_build_tasks.len()
);
}
}

Expand All @@ -65,49 +59,71 @@ impl Buildings {
.expect("world_constants should be set!")
}

pub fn build_async(&mut self, city: Dictionary) {
let city = match crate::world::city_data::City::try_from_dict(&city)
.context("Failed to deserialize city data")
{
Ok(v) => v,
Err(err) => {
logger::error!("{:?}", err);
return;
}
};
pub fn build_async(&mut self, city: Dictionary) -> Gd<GodotFuture> {
let world_constants = self.world_constants().clone();
let mut base = self.base.clone();
let mut slf: RsRef<Self> = base.to_script();
let tree = base.get_tree().expect("Node must be part of the tree!");
let (resolve, godot_future) = async_support::godot_future();

let handle = godot_task(async move {
let city_coords_feature = CityCoordsFeature::new(world_constants, sea_level);
let next_tick = builtin::Signal::from_object_signal(&tree, "process_frame");
let time = Time::singleton();

let city = match crate::world::city_data::City::try_from_dict(&city)
.context("Failed to deserialize city data")
{
Ok(v) => v,
Err(err) => {
logger::error!("{:?}", err);
return;
}
};

let sea_level = city.simulator_settings.sea_level;
let buildings = city.buildings;
let tiles = city.tilelist;

let sea_level = city.simulator_settings.sea_level;
let buildings = city.buildings;
let tiles = city.tilelist;
logger::info!("starting to load buildings...");

self.city_coords_feature =
CityCoordsFeature::new(self.world_constants().to_owned(), sea_level);
let mut count = 0;
let mut start = time.get_ticks_msec();

logger::info!("starting to load buildings...");
for building in buildings.into_values() {
if (time.get_ticks_msec() - start) > Self::TIME_BUDGET {
slf.emit_progress(count);
count = 0;
start = time.get_ticks_msec();

let _: () = next_tick.to_future().await;
}

count += 1;

let mut job_runner = LocalJobRunner::new(
move |host: &mut Self, building: city_data::Building| {
if building.building_id == 0x00 {
logger::info!("skipping empty building");
return;
continue;
}

host.insert_building(building, &tiles);
},
50,
);
Self::insert_building(&mut base, building, &tiles, &city_coords_feature);
}

let buildings_array = buildings.into_values().collect();
slf.emit_progress(count);

job_runner.tasks(buildings_array);
resolve(());
});

self.job_runner = Some(job_runner);
self.pending_build_tasks.push(handle);
godot_future
}

/// Insert a new building into the world.
fn insert_building(
&mut self,
base: &mut Gd<Node>,
building: city_data::Building,
tiles: &BTreeMap<(u32, u32), city_data::Tile>,
city_coords_feature: &CityCoordsFeature,
) {
let building_size = building.size;
let name = building.name.as_str();
Expand Down Expand Up @@ -137,12 +153,12 @@ impl Buildings {
size: 2,
};

self.insert_building(spawn_building, tiles);
self.spawn_point_encountered.emit((
Self::insert_building(base, spawn_building, tiles, city_coords_feature);
CastToScript::<Buildings>::to_script(base).emit_spawn_point_encountered(
Array::from(&[tile_coords.0, tile_coords.1]),
2,
altitude,
));
);
}

let (Some(mut instance), instance_time) =
Expand All @@ -161,7 +177,7 @@ impl Buildings {
instance.set("tile_coords_array".into(), array.to_variant());
}

let mut location = self.city_coords_feature.get_building_coords(
let mut location = city_coords_feature.get_building_coords(
tile_coords.0,
tile_coords.1,
altitude,
Expand All @@ -172,16 +188,12 @@ impl Buildings {
location.y += 0.1;

let (_, insert_time) = with_timing(|| {
self.get_sector(tile_coords)
Self::get_sector(base, tile_coords, city_coords_feature)
.add_child_ex(instance.clone().upcast())
.force_readable_name(true)
.done();

let Some(root) = self
.base
.get_tree()
.and_then(|tree| tree.get_current_scene())
else {
let Some(root) = base.get_tree().and_then(|tree| tree.get_current_scene()) else {
logger::warn!("there is no active scene!");
return;
};
Expand All @@ -206,7 +218,11 @@ impl Buildings {
}

/// sector coordinates are expected to align with a step of 10
fn get_sector(&mut self, tile_coords: (u32, u32)) -> Gd<Node3D> {
fn get_sector(
base: &mut <Self as GodotScript>::Base,
tile_coords: (u32, u32),
city_coords_feature: &CityCoordsFeature,
) -> Gd<Node3D> {
const SECTOR_SIZE: u32 = 32;

let sector_coords = (
Expand All @@ -220,75 +236,40 @@ impl Buildings {
format!("{}_{}", x, y)
};

self.base
.get_node_or_null(sector_name.to_godot().into())
base.get_node_or_null(sector_name.to_godot().into())
.map(Gd::cast)
.unwrap_or_else(|| {
let mut sector: Gd<Node3D> = Marker3D::new_alloc().upcast();

sector.set_name(sector_name.to_godot());

self.base.add_child(sector.clone().upcast());
base.add_child(sector.clone().upcast());

sector.translate(self.city_coords_feature.get_world_coords(
sector.translate(city_coords_feature.get_world_coords(
sector_coords.0 + (SECTOR_SIZE / 2),
sector_coords.1 + (SECTOR_SIZE / 2),
0,
));

if let Some(root) = self
.base
.get_tree()
.and_then(|tree| tree.get_current_scene())
{
if let Some(root) = base.get_tree().and_then(|tree| tree.get_current_scene()) {
sector.set_owner(root);
};

sector
})
}
}

type LocalJob<T, H> = Box<dyn Fn(&mut H, T)>;

#[derive(Dbg)]
struct LocalJobRunner<T, H>
where
T: Debug,
{
budget: u64,
tasks: VecDeque<T>,
#[dbg(skip)]
callback: LocalJob<T, H>,
}

impl<T: Debug, H> LocalJobRunner<T, H> {
fn new<C: Fn(&mut H, T) + 'static>(callback: C, budget: u64) -> Self {
Self {
callback: Box::new(callback),
tasks: VecDeque::new(),
budget,
}
pub fn emit_spawn_point_encountered(&self, tile_coords: Array<u32>, size: u8, altitide: u32) {
self.spawn_point_encountered
.emit((tile_coords, size, altitide))
}

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
pub fn emit_progress(&self, new_building_count: u32) {
self.loading_progress.emit(new_building_count);
}

fn tasks(&mut self, mut tasks: VecDeque<T>) {
self.tasks.append(&mut tasks);
pub fn emit_ready(&self) {
self.ready.emit(());
}
}

Expand Down
Loading

0 comments on commit 97f74fe

Please sign in to comment.