Как заставить работать асинхронную систему в игровом движке bevy?

В настоящее время я работаю над игрой на основе 3D-вокселей, и я хочу иметь процедурно сгенерированные фрагменты, основанные на движении игрока.

Но запуск генерации чанков в простой системе приводит к огромным падениям FPS.

Я уже пытался создать пул задач, который работает независимо от всего остального, используя std::sync::mpsc::channel, чтобы генерировать данные фрагментов и сетку, которые затем запрашиваются обычной сборочной системой, буферизуются, а затем создаются с помощью commands.spawn(PbrBundle{...}).

fn chunk_loader(
    pool: Res<Pool>,
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut chunkmap: ResMut<ChunkMap>,
    mut buffer: ResMut<Buffer>, // buffer of chunk data
) {
    let mut chunks = pool.request_chunks();
    buffer.0.append(&mut chunks);

    for _ in 0..CHUNK_UPDATES_PER_FRAME {
        if let Some( (chunk, mesh) ) = buffer.0.pop() {
            chunkmap.map.insert([chunk.x, chunk.y, chunk.z], chunk);
    
            let mesh = mesh.clone();
        
            commands.spawn_bundle(PbrBundle {
                mesh: meshes.add(mesh),
                transform: Transform::from_matrix(Mat4::from_scale_rotation_translation(
                    Vec3::splat(1.0),
                    Quat::from_rotation_x(0.0),
                    Vec3::new((chunk.x * CHUNK_SIZE as i64) as f32, (chunk.y * CHUNK_SIZE as i64) as f32, (chunk.z * CHUNK_SIZE as i64) as f32),
                )),
                material: materials.add(StandardMaterial {
                    base_color: Color::hex("993939").unwrap(),
                    perceptual_roughness: 0.95,
                    ..default()
                }),
                ..default()
            }).insert(super::LoadedChunk{x: chunk.x, y: chunk.y, z: chunk.z, should_unload: false});
        }
    }
}

Но это не помогает, потому что все равно занимает слишком много времени.

Что мне нужно, так это способ выполнить генерацию и порождение чанков асинхронным способом, который не влияет на частоту кадров, но я не знаю, как мне к этому подойти.

bevy::prelude::AsyncComputeTaskPool может выполнять свою работу, но я не могу найти никакой документации или примеров, поэтому я не знаю, что именно он делает, кроме асинхронной параллельной итерации по запросам.

Я никогда не писал асинхронный код, может ли кто-нибудь мне помочь?

РЕДАКТИРОВАТЬ

Я обнаружил, что описанная выше система на самом деле работает довольно хорошо.

Проблема в системе, которую я использую для проверки того, какие фрагменты загружать. Я использую HashMap для хранения каждого фрагмента, и каждый раз, когда игрок перемещается, я проверяю несколько фрагментов, если они уже созданы, и, если нет, отправляю запрос в пул задач для этого.

fn chunk_generation(
    mut query: Query<(&Transform, &mut Player)>,
    mut chunks: ResMut<ChunkMap>,
    pool: Res<Pool>,
) {
    let mut player_pos = Vec3::ZERO;
    let mut player_moved: bool = false;
    for (transform, mut player) in query.iter_mut().next() {
        player_pos = transform.translation;
        if player.chunks_moved_since_update > 0 {
            player_moved = true;
            player.chunks_moved_since_update = 0;
        }
    }

    let chunk_x = (player_pos.x / CHUNK_SIZE as f32).round() as i64;
    let chunk_y = (player_pos.y / CHUNK_SIZE as f32).round() as i64;
    let chunk_z = (player_pos.z / CHUNK_SIZE as f32).round() as i64;

    // loading
    if player_moved {
        let mut chunks_to_load: Vec<[i64; 3]> = Vec::new();

        for x in -RENDER_DISTANCE_HOR..RENDER_DISTANCE_HOR {
            for y in -RENDER_DISTANCE_VER..RENDER_DISTANCE_VER {
                for z in -RENDER_DISTANCE_HOR..RENDER_DISTANCE_HOR {

                    let chunk_x = chunk_x as i64 + x;
                    let chunk_y = chunk_y as i64 + y;
                    let chunk_z = chunk_z as i64 + z;
                    let chunk_coords = [chunk_x, chunk_y, chunk_z];

                    // check if chunk is not in world
                    if !chunks.map.contains_key(&chunk_coords) {
                        println!("generating new chunk");
                        let chunk = Chunk::new(chunk_x, chunk_y, chunk_z);
                        chunks.map.insert(chunk_coords, chunk);
            
                        chunks_to_load.push([chunk_x, chunk_y, chunk_z]);
                    }
                }
            }
        }

        pool.send_chunks(chunks_to_load);
    }
}

Можно ли сделать и этот асинхронный?

Асинхронная передача данных с помощью sendBeacon в JavaScript
Асинхронная передача данных с помощью sendBeacon в JavaScript
В современных веб-приложениях отправка данных из JavaScript на стороне клиента на сервер является распространенной задачей. Одним из популярных...
0
0
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

bevy::tasks::AsyncComputeTaskPool обернуть в bevy::tasks::Task был метод, который я искал.

Теперь у меня есть система, которая вставляет задачу в каждый фрагмент, чтобы сгенерировать себя, а затем опрашивает эту задачу в другой функции, подобной этой:

#[derive(Component)]
pub struct GenTask {
    pub task: Task<Vec<([i64; 3], ChunkData)>>
}

pub fn prepare_gen_tasks(
    mut queue: ResMut<ChunkGenQueue>,
    chunks: Res<ChunkMap>,
    pool: Res<AsyncComputeTaskPool>,
    mut cmds: Commands,
) {
    while let Some(key) = queue.queue.pop() {
        if let Some(entity) = chunks.entity(key) {

            let task = pool.spawn(async move {
                let chunk = Chunk::new(key);
                super::generation::generate(chunk)
            });

            cmds.entity(entity).insert(GenTask{task});
        }
    }
}

pub fn apply_gen_tasks(
    mut voxels: ResMut<VoxelMap>,
    mut query: Query<(Entity, &mut GenTask)>,
    mut mesh_queue: ResMut<ChunkMeshQueue>,
    mut cmds: Commands,
) {
    query.for_each_mut(|(entity, mut gen_task)| {
        if let Some(chunks) = future::block_on(future::poll_once(&mut gen_task.task)) {
            for (key, data) in chunks.iter() {
                voxels.map.insert(*key, *data);

                mesh_queue.queue.push(*key);
            }
            cmds.entity(entity).remove::<GenTask>();
        }
        return;
    });
}

Другие вопросы по теме